Add a plugin field to Block Factory

Blockly Developer Tools lets you create custom blocks using blocks! It has support for fields that are published as plugins in addition to the fields that come with core Blockly. If you've created a custom field, you can add support for it to the Block Factory by following this guide. The custom field must be published on npm before you can add support for it. You also need to commit to updating your field to keep up with changes in Blockly, otherwise we may need to remove it from Block Factory in the future.

Development on the Block Factory

The source code for the Block Factory is located in the blockly-samples repository in the examples/developer-tools directory.

To submit a change to the Developer Tools in blockly-samples, you'll need to follow the typical steps for developing in blockly-samples. Unlike working with plugins however, you'll need to run npm install from the examples/developer-tools directory directly, rather than at the root level of blockly-samples.

Install the plugin

In order for the Block Factory to show your custom field in the preview, it needs to install the custom field. Add your field as an npm dependency of developer-tools. Then, register it or do any other setup work necessary in developer-tools/src/blocks/index.ts.

Create a block for the field

Since the Block Factory uses blocks to create custom blocks, you'll need a block that represents your custom field.

Create the block definition

You need to design the block for your field; if you want to get meta, you can even design it using Block Factory! The block should allow the user to configure the setup required by your field, such as default values, and a name. Add this block definition to developer-tools/src/blocks/fields.ts, and import it in developer-tools/src/blocks/index.ts.

Add block to toolbox

Next, you need to add this block to the toolbox definition to make it accessible to users. The toolbox definition is located in developer-tools/src/toolbox.ts. Your block should be added to the "Fields" category.

Code Generators

The Block Factory works by using the Code Generator system you are already familiar with from Blockly. Each block has a block-code generator for each type of output generated by the Block Factory, and the parent blocks assemble the code for the child blocks into the correct output. To add support for a custom field, you'll need to add block-code generator functions for each of the Code Generator classes.

Create a file for your field block in the output-generators/fields directory. You'll add the block-code generators for each of the following Generators to this file. Import this file in the blocks/index.ts file so that the block-code generator functions are loaded into the application.

JavaScript definition

The javascriptDefinitionGenerator creates the code that will be included in the JavaScript definition for a block that includes your custom field. Usually, this means the block-code generator should return a line of code that looks like .appendField(new YourFieldConstructor(arg1, arg2), 'userSpecifiedName'). Note that this line of code does not include a semicolon, because an input that contains multiple fields will have several calls to appendField chained together. The arguments in the constructor are pulled from the values the user set on the field block. Here's an example of this block-code generator for FieldAngle:

javascriptDefinitionGenerator.forBlock['field_angle'] = function (
  block: Blockly.Block,
  generator: JavascriptDefinitionGenerator,
): string {
  const name = generator.quote_(block.getFieldValue('FIELDNAME'));
  const angle = block.getFieldValue('ANGLE');
  return `.appendField(new FieldAngle(${angle}), ${name})`;
};

The angle block that the user dragged from the "Fields" category of the Block Factory toolbox has two fields on it:

  • FIELDNAME: user can set the name of the field on their custom block
  • ANGLE: user can set the default angle value

In this block-code generator, we get the default angle value and pass it as the only argument to the FieldAngle constructor. The field name is always passed as the second argument to appendField.

JSON definition

The jsonDefinitionGenerator is similar, but this outputs the part of the JSON block definition that corresponds to your field. Typically, this code is a JSON object that includes:

  • type: corresponds to the name of your field in the Blockly field registry
  • name: user can set the name of the field on their custom block
  • any other custom properties needed by your field's json initialization method.

Here's an example from the FieldAngle again:

jsonDefinitionGenerator.forBlock['field_angle'] = function (
  block: Blockly.Block,
  generator: JsonDefinitionGenerator,
): string {
  const code = {
    type: 'field_angle',
    name: block.getFieldValue('FIELDNAME'),
    angle: block.getFieldValue('ANGLE'),
  };
  return JSON.stringify(code);
};

Code headers

The Code Header Generator creates the Code Headers output shown in the Block Factory. This output can be toggled between esmodule imports and script tags, depending on how the user wishes to load the code, so there are actually two different generator instances: one for each case. You need to add a block-code generator for each of them. Here's an example for FieldAngle:

importHeaderGenerator.forBlock['field_angle'] = function (
  block: Blockly.Block,
  generator: CodeHeaderGenerator,
): string {
  generator.addHeaderLine(
    `import {registerFieldAngle, FieldAngle} from '@blockly/field-angle';`,
  );
  generator.addHeaderLine(`registerFieldAngle();`);
  return '';
};

scriptHeaderGenerator.forBlock['field_angle'] = function (
  block: Blockly.Block,
  generator: CodeHeaderGenerator,
): string {
  generator.addHeaderLine(
    `<script src="https://unpkg.com/@blockly/field-angle"></script>`,
  );
  generator.addHeaderLine(`registerFieldAngle();`);
  return '';
};

These generators have a method called addHeaderLine that lets you specify a line of code that should be called before your field is used in code. Commonly, this includes work such as importing the field or loading it through a script tag, and perhaps calling a function that will register the field with Blockly's field registry.

For these two block-code generators, all the code should be added through calls to addHeaderLine. This function will ensure each header line is only shown once, even if your custom field block is used multiple times in one custom block. The block-code generator should return the empty string.

Generator stub

Lastly, we have the generator that creates the generator stub for the field. In this block-code generator, you'll be writing code that generates code that helps the user write code that generates code. Confused yet? It's easier than it sounds!

The generator stub for a custom block includes a premade variable representing every field on the block. Then there's a TODO the user must finish to assemble all of these variables into the final code string their custom block will return. That means typically all your block-code generator needs to do is return the line that creates this custom variable. Say the user is making a custom block that will add rays of sunlight to their canvas. They add an angle field to the block and name it "SUN_DIRECTION". The generator stub for this block would include the line const angle_sun_direction = block.getFieldValue("SUN_DIRECTION");. That is the line of code our block-code generator for the angle field needs to return:

generatorStubGenerator.forBlock['field_angle'] = function (
  block: Blockly.Block,
  generator: GeneratorStubGenerator,
): string {
  const name = block.getFieldValue('FIELDNAME');
  const fieldVar = generator.createVariableName('angle', name);
  return `const ${fieldVar} = block.getFieldValue(${generator.quote_(
    name,
  )});\n`;
};

To get a standardized name for the variable, you can call generator.createVariableName and pass in the type of your field (such as angle, number, etc.) along with what the user named their field.

Test it

After you've written all of these pieces, you should be able to start the Block Factory by running npm start in the blockly-samples/examples/developer-tools directory. You should be able to drag your block from the field category, add it to an input on a block, and watch the output change. Check that the preview of the block looks right, and that the code for each of the output sections is correct.