Skip to main content

Block structure in JavaScript

In this document, we'll discuss how to use JavaScript to define the inputs and fields (including labels) in your block. If you're not familiar with these terms, see Anatomy of a block before proceeding.

You can also define your inputs, fields, and connections in JSON.

Append inputs

The JavaScript API includes an append method for each input type:

init: function() {
// ...
this.appendEndRowInput()
.appendField('for each')
.appendField('item')
.appendField(new Blockly.FieldVariable(), 'VAR');
this.appendValueInput('LIST')
.setCheck('Array')
.setAlign(Blockly.inputs.Align.RIGHT)
.appendField('in list');
this.appendStatementInput('DO')
.appendField('do');
this.appendDummyInput()
.appendField('end');
}
A block with four rows. The first row has the labels "for each" and "item" and a variable dropdown with "x" chosen. The second row has the label "in list" and a value input. The third row has the label "do" and a statement input. And the last row has the label "end".

Each appendInput method can take an identifier string, which is used by code generators to retrieve code for the block connected to the input. Code generators rarely reference dummy and end-of-row inputs, so there is generally no reason to assign them an identifier.

The JavaScript API also includes a generic appendInput method for appending custom inputs. Note that in this case, the identifier should be passed directly to your custom input's constructor.

{
// ...
this.appendInput(new MyCustomInput('INPUT_NAME')).appendField(
'an example label',
)
}

All of the appendInput methods (both generic and non-generic) return the input object so that they can be further configured using method chaining. There are three built-in methods used for configuring inputs.

Input labels

Inputs on blocks have default accessibility labels that are derived from the surrounding text on the block. For example, the "in list" value input on the for each block above is labeled in list, which comes from the label field that precedes it. A screen reader uses this label to describe the input's empty connection and to give context to any block plugged into it.

For some custom blocks, this derived label does not provide enough information. Consider a block that builds a coordinate point:

A block with the label "point (", a value input, the label ",", another value input, and the label ")".

Both value inputs are preceded only by punctuation, so the highlighted input's derived label would read ,, which tells assistive technology users nothing about what belongs there. To provide more information, you can set custom labels. In this case, "x coordinate" and "y coordinate" would be appropriate.

You can set the label for an input by calling setAriaLabelProvider on the input in the block definition. The provider can be either a string or a function that returns a string, to support dynamic labels.

init: function() {
// ...
this.appendValueInput('X')
.setAriaLabelProvider('x coordinate')
.appendField('point (');
this.appendValueInput('Y')
.setAriaLabelProvider('y coordinate')
.appendField(',');
this.appendDummyInput()
.appendField(')');
}

Passing a function instead of a string lets you build the label dynamically, for example from the block's current field values. The function receives the Input and returns a string (or null to fall back to the default label).

init: function() {
// ...
this.appendValueInput('DURATION')
.setAriaLabelProvider((input) => {
const units = input.getSourceBlock().getFieldValue('UNITS');
return `duration in ${units}`;
})
.appendField('wait');
}

To provide a localized label, pass a string containing a Blockly.Msg reference of the form %{BKY_...}. Blockly replaces the reference with the corresponding message at render time.

init: function() {
// ...
this.appendValueInput('X')
.setAriaLabelProvider('%{BKY_POINT_X_ARIA_LABEL}')
.appendField('point (');
}

You can set the same labels in JSON.

Append fields

Once an input has been created and appended to a block with appendInput, one may optionally append any number of fields to the input. These fields are often used as labels to describe what each input is for.

init: function() {
// ...
this.appendDummyInput()
.appendField('hello');
}

A block with the label "hello".

The simplest field is a label. Blockly's convention is to use all lowercase text, with the exception of proper names (e.g. Google, SQL).

An input row can contain any number of fields. Multiple appendField calls may be chained together to efficiently add several fields to the same input row.

init: function() {
// ...
this.appendDummyInput()
.appendField('hello')
.appendField(new Blockly.FieldLabel('Neil', 'person'));
}

A block with the label "hello" in plain text and the label "Neil" in
italics.

The appendField('hello') call is actually a shortcut for using an explicit FieldLabel constructor: appendField(new Blockly.FieldLabel('hello')). The only time one would wish to use the constructor is when specifying a class name so that the label may be styled using a CSS rule.

Field labels

Similar to inputs, every field has an accessibility label. The default label is determined by the type and value of the field, in the form <type>: <value>. For example, a FieldNumber with the value 123 is labeled number: 123.

For some blocks, you might want to use a more specific description for the type of field than simply "number" or "dropdown". A block that sets the speed of a motor might want to label its number field as "speed", for example, so that a screen reader announces speed: 100 instead of number: 100.

There are two ways to set the type name for a particular field instance on a block.

The simplest is to pass an ariaTypeName in the field's constructor configuration object. This object is the field's last constructor argument (its position depends on the field type), and is where you configure other field options too.

init: function() {
// ...
this.appendDummyInput()
.appendField('set motor speed to')
.appendField(
new Blockly.FieldNumber(
100, // value
0, // min
255, // max
undefined, // precision
undefined, // validator
{ariaTypeName: 'speed'}, // config
),
'SPEED');
}

Alternatively, call setAriaTypeName on the field:

init: function() {
// ...
const speed = new Blockly.FieldNumber(100, 0, 255);
speed.setAriaTypeName('speed');
this.appendDummyInput()
.appendField('set motor speed to')
.appendField(speed, 'SPEED');
}

Either way, the type name may contain a Blockly.Msg reference of the form %{BKY_...} for localization, the same as input labels and role descriptions.

To set the label for all instances of a particular type of field, override getAriaTypeName in a custom field instead of setting it per instance.

You can set the type name in JSON as well.

Connection checks

init: function() {
// ...
this.appendValueInput('NUM')
.setCheck('Number');
}

The setCheck method is used for type-checking connections. If given an argument of null, the default, then this input may be connected to any block. See Connection checks for details.

Align fields

init: function() {
// ...
this.appendValueInput('LIST')
.appendField('in list')
.setAlign(Blockly.inputs.Align.RIGHT);
}

The setAlign method is used to align fields within an input. There are three self-descriptive values which may be passed as an argument to this function: Blockly.inputs.Align.LEFT, Blockly.inputs.Align.RIGHT, and Blockly.inputs.Align.CENTER.

When a block is rendered in right-to-left mode (e.g. Arabic and Hebrew), left and right are reversed. Thus Blockly.inputs.Align.RIGHT would align fields to the left.

Accessibility role descriptions

All blocks have an aria-roledescription so that screen readers announce the block with a useful role rather than the default "graphics element".

The default roles Blockly uses are based on the connections available on a block. They are:

  • container (has at least one statement input)
  • value (has an output connection)
  • statement (all other blocks)

These role descriptions are localized. Note that we do not include the word "block" in the role description, as feedback from users was that including the word "block" was too repetitive and noisy in the screen reader output.

Custom role descriptions

In some situations, you may want to set a custom role description on your custom blocks. We recommend that in most cases you use the default role descriptions rather than setting a custom role description for every block. Generally, the text on the block, forming the aria label for it, should tell users what the block does. The role description gives users a hint about the shape of the block and what kind of connections it can form.

If you have blocks that are a different shape from other blocks, that may be a good use case for setting a custom role description. For example, boolean blocks in the Zelos renderer have a hexagonal shape and can only be put into certain input connections, so they are different from other "value" blocks. You can set a custom role description by calling setAriaRoleDescriptionProvider in the block definition. As with input labels, you can pass either a string or a function that returns a string.

init: function() {
// ...
this.setAriaRoleDescriptionProvider('boolean');
}

To localize the role description, pass a string containing a Blockly.Msg reference of the form %{BKY_...}; Blockly replaces it at render time.

init: function() {
// ...
this.setAriaRoleDescriptionProvider('%{BKY_BOOLEAN_ROLE_DESCRIPTION}');
}

You can set a custom role description in JSON as well.

note

By default, Blockly does not set boolean blocks in Zelos to a "boolean" role description, as not all users will be familiar with this term. However, you can do this in your own application if the term makes sense for your users.