Custom Blocks: Block Paradigms

There are several paradigms to choose from when designing an application which uses Blockly. Consideration of these choices should be made early, since they affect the blocks the user will need.

Configuration

Many Blockly applications are used to describe configurations, rather than executable programs. Configuration applications typically start by initializing one root level block on the workspace. A good example is the Block Factory tab of the Blockly Developer Tools:

Blockly.Blocks['factory_base'] = {
  init: function() {
    this.setDeletable(false);
    this.setMovable(false);
    this.setEditable(false);
    // etc...
  }
}

Blockly.serialization.blocks.append({'type': 'factory_base'}, workspace);

This creates an undeletable, unmovable block that that holds all the user's configuration. The workspace may be serialized at any time to determine the current configuration.

Such applications may wish to automatically disable any block not connected to the root block. This can be accomplished on the with one line:

workspace.addChangeListener(Blockly.Events.disableOrphans);

Serial Program

The majority of Blockly applications are designed to create serial programs. Users stack together blocks which are executed in order.

Every (non-disabled) block on the workspace will form part of the program. If there are multiple stacks of blocks, higher ones are executed first. (If two stacks are approximately the same height, stacks to the left (right in RTL mode) are given priority.)

The workspace may be exported to executable code at any time. This code may be executed client side in JavaScript (using eval or the JS Interpreter), or server side in any language.

import {javascriptGenerator} from 'blockly/javascript';

var code = javascriptGenerator.workspaceToCode(workspace);

Parallel Program

Some Blockly applications choose to execute all stacks of blocks in parallel, rather than serially. An example would be a music application where a drum loop runs concurrently with a melody.

One way of implementing parallel execution is to generate the code for each block individually:

import {javascriptGenerator} from 'blockly/javascript';

var json = Blockly.serialization.workspaces.save(workspace);

// Store top blocks separately, and remove them from the JSON.
var blocks = json['blocks']['blocks'];
var topBlocks = blocks.slice();  // Create shallow copy.
blocks.length = 0;

// Load each block into the workspace individually and generate code.
var allCode = [];
var headless = new Blockly.Workspace();
for (var i = 0; block < topBlocks.length; i++) {
  var block = topBlocks[i];
  blocks.push(block);
  Blockly.serialization.workspaces.load(json, headless);
  allCode.push(javascriptGenerator.workspaceToCode(headless));
  blocks.length = 0;
}

If the target language is JavaScript, the allCode array may then be used to create multiple JS Interpreters for simultaneous execution. If the target language is something like Python, then the allCode array may be assembled into a single program that uses a threading module.

As with any parallel program, careful decisions must be made regarding any shared resources such as variables and functions.

Event Driven Program

Event handlers are just functions that get called by the system, rather than by the program. These blocks can either enclose the stack of blocks to be executed, or they may be headers that sit on top of a stack of blocks.

Some developers like to add a 'hat' to the top of event blocks so that they look distinct from other blocks. This is not the default look for Blockly, but it may be added by either overriding the renderer constant ADD_START_HATS to true (Custom renderers codelab - Override constants). or by adding a theme and setting the hat option on the block style. More information for setting hats on blocks as part of themes can be found here.

Within an event-driven model, it might make sense to also make a handler for the program start. Under this model, any block on the workspace not connected to an event handler would be ignored and would not execute.

When designing a system that uses events, consider whether it is possible or desirable to support multiple instances of the same event handler.