What you'll learn

This codelab will teach you how to modify a simple web app to include the Blockly visual programming library.

What is Blockly?

Blockly is a library for building block programming apps.

Block programming allows users to create scripts and programs by using visual blocks, even if they do not know any programming language.

Blockly includes everything you need for defining and rendering blocks in a drag-n-drop editor. Each block represents a chunk of code that can be easily stacked and translated into code.

What you'll build

MusicMaker, a web app where you can program buttons to play different sounds, using Blockly.

image

image

What you'll need

This codelab is focused on Blockly. The app structure, non-relevant concepts and code are glossed over and are provided for you to simply copy and paste.

Download the sample code

You can get the sample code for this code by either downloading the zip here:

Download zip

or by cloning this git repo:

git clone https://github.com/google/blockly-samples.git

If you downloaded the source as a zip, unpacking it should give you a root folder named blockly-samples-master.

The relevant files are in examples/getting-started-codelab. There are two versions of the app:

Each folder contains:

To run the app, simply open starter-code/index.html in a browser.

Play Mode

By default, the app launches in "Play Mode". In this mode, you can see 9 buttons. None of them can do anything yet. The idea is to let the user define custom behaviors for each button, using Blockly.

image

Edit mode

By tapping the EDIT button, you can switch to edit mode. In this mode, tapping a button will display an editor, which is where you can program how sounds should play for that button. For now, the editor screen is empty.

image

You can go back to the play mode by tapping SAVE and then DONE buttons.

Now that you know what you'll be building, you need to add Blockly dependencies to your app.

Blockly releases are published on npm on a quarterly basis. For this codelab you will import blockly using unpkg, which lets you import all of the files you need with a single script tag.

Add the script tag

Open starter-code/index.html in a text editor and scroll to the end. You can see two script tags:

<script src="scripts/music_maker.js"></script>
<script src="scripts/main.js"></script>

Add Blockly just before these two scripts. The order is important, because you will use Blockly objects later in main.js. Your imports should now look like this:

<script src="https://unpkg.com/blockly"></script>
<script src="scripts/music_maker.js"></script>
<script src="scripts/main.js"></script>

Default imports

Importing Blockly this way loads four default modules.

Alternate imports

There are many ways to import a library in JavaScript, and this tutorial does not cover all of them. For samples that show how to integrate Blockly in your project, look at the examples folder in blockly-samples.

You can also define your imports more carefully to get different generators and locales.

In this section you will learn how to add a workspace to your app, including how to define a toolbox.

Parts of Blockly

A Blockly workspace has two main components:

image

The toolbox may be organized into categories, and may contain both single blocks and groups of blocks. A well-organized toolbox helps the user to explore the available blocks and understand the capabilities of the underlying system.

A toolbox is defined as a JavaScript object and passed into the workspace constructor through an options struct.

For more information on this JSON format and toolbox configuration, including category creation, please see our toolbox documentation.

Define the toolbox

Open up scripts/main.js and scroll down to the end of the file. Then add the code for your toolbox definition just after the call to enableMakerMode():

const toolbox = {
  'kind': 'flyoutToolbox',
  'contents': [
    {
      'kind': 'block',
      'type': 'controls_repeat_ext',
      'inputs': {
        'TIMES': {
          'shadow': {
            'type': 'math_number',
            'fields': {
              'NUM': 5
            }
          }
        }
      }
    }
  ]
};

This JavaScript object defines a toolbox with a single "repeat loop" block.

Injection

Adding a Blockly workspace to a page is called injection, because the workspace is injected into a div that already exists on the page.

To do this you call the function Blockly.inject(container, options), which takes two arguments:

For this codelab we will inject into a div with the id "blocklyDiv", which you can find in index.html:

<div id="blocklyDiv" style="height: 480px; width: 400px;"></div>

Create the workspace

Now add code to inject the Blockly editor just after the code you used to define your toolbox:

Blockly.inject('blocklyDiv', {
  toolbox: toolbox,
  scrollbars: false,
  horizontalLayout: true,
  toolboxPosition: "end",
});

Let's look at the options we used to initialize your blockly editor:

The options struct gives you significant control over your Blockly instance. You can pass options to set Blockly's theme, modify scrolling behaviour, set the renderer, and more. For more information, head over to Blockly's developer site and check out the configuration section.

Check your work

Now refresh the page. Select the EDIT mode, then tap on one of the buttons. You should see a Blockly editor:

image

Drag and drop the available loop block to the workspace to test it out.

Since this is a music maker app, we want a block that plays sounds. We could create one block per sound, but instead we will create a single block with a dropdown to select which note to play:

image

In Blockly, a block definition describes how a block looks and behaves. This includes its text, colour, and shape. It may also include which other blocks it can connect to.

Blocks can be defined in either JavaScript or JSON. The developer site has a full article on how to define a block.

In this codelab we will simply provide the block definition for you to copy and use.

Define the sound block

Create a JS file to define a new "play sound" block:

  1. Add sound_blocks.js file in the scripts directory.
  2. Add the following code to sound_blocks.js:
Blockly.common.defineBlocksWithJsonArray([
  {
    "type": "play_sound",
    "message0": "Play %1",
    "args0": [
      {
        "type": "field_dropdown",
        "name": "VALUE",
        "options": [
          ["C4", "sounds/c4.m4a"],
          ["D4", "sounds/d4.m4a"],
          ["E4", "sounds/e4.m4a"],
          ["F4", "sounds/f4.m4a"],
          ["G4", "sounds/g4.m4a"]
        ]
      }
    ],
    "previousStatement": null,
    "nextStatement": null,
    "colour": 355
  }
]);

Add a script tag to index.html to include your new block definition:

<script src="scripts/sound_blocks.js"></script>

Your sound block definitions must come after importing Blockly and before the other imports, since you will use Blockly functions in this file, and you will be using functions from this file in later files. Your imports should now look like this:

<script src="https://unpkg.com/blockly"></script>
<script src="scripts/sound_blocks.js"></script>
<script src="scripts/music_maker.js"></script>
<script src="scripts/main.js"></script>

Add the sound block to the toolbox

Now we can update the toolbox to include the new sound block, by adding {'kind': 'block', 'type': 'play_sound'} to our toolbox definition:

const toolbox = {
  'kind': 'flyoutToolbox',
  'contents': [
    {
      'kind': 'block',
      'type': 'controls_repeat_ext',
      'inputs': {
        'TIMES': {
          'shadow': {
            'type': 'math_number',
            'fields': {
              'NUM': 5
            }
          }
        }
      }
    },
    {
      'kind': 'block',
      'type': 'play_sound'
    }
  ]
};

Run the app one more time, and play around with the new Play (sound) block. It should look like this:

image

The block factory

This step discussed how to manually define custom blocks in Blockly. Once you've completed the entire codelab, we recommend that you check out our block factory tool, which helps automate part of this process.

You now have a Blockly workspace that appears when editing a button. The user can use this to write code, but there's no link between the code the user has written and an individual button on the page.

Once the button behavior is defined by the user, it needs to be saved for later use. The saved code must be per button, since buttons can be programmed to play different sounds.

Add the save method

Open scripts/main.js. Add the following code to the save() method:

button.blocklySave = Blockly.serialization.workspaces.save(
    Blockly.getMainWorkspace());

workspaces.save takes the Blockly workspace, exports its state to a JavaScript object and stores it in a blocklySave property on the button. This way the exported state for the block sequence gets associated with a particular button.

Add the load method

Similarly, when a user opens the editor, the blocks previously associated with this button should get loaded into the workspace.

In the scripts/main.js file, add loadWorkspace function:

function loadWorkspace(button) {
  const workspace = Blockly.getMainWorkspace();
  if (button.blocklySave) {
    Blockly.serialization.workspaces.load(button.blocklySave, workspace);
  } else {
    workspace.clear();
  }
}

This loads the blocks stored on the button that was clicked back into the workspace.

Call this function from the end of the function enableBlocklyMode:

function enableBlocklyMode(e) {
  ...
  loadWorkspace(currentButton);
}

Now, test the code. Edit the workspace for one of the buttons, add some blocks, save it, and reopen it. The workspace should still contain the blocks you added.

Now that each button can be configured with its own Blockly workspace, the next thing we want to do is to generate JavaScript code from each workspace.

This generated code will be run by the browser, effectively executing the blocks set up in the Blockly workspace.

The language generator

Blockly can generate code from blocks for different languages, e.g. JavaScript, Python, or PHP.

A language generator defines the rules for generating a specific language (such as indentation). Because we are using the default imports, we don't need to add any new code to get the JavaScript generator.

As previously mentioned, you can define your imports more carefully to get a different generator.

Add a block generator

When Blockly generates JavaScript code for blocks in a workspace, it translates each block into code. By default, it knows how to translate all library-provided default blocks into JavaScript code. However, for any custom blocks, we need to specify our own translation functions. These are called block generators.

Add the following code to the bottom of scripts/sound_blocks.js:

javascript.javascriptGenerator.forBlock['play_sound'] = function(block) {
  let value = '\'' + block.getFieldValue('VALUE') + '\'';
  return 'MusicMaker.queueSound(' + value + ');\n';
};

With this translation function, the following play_sound block:

image

translates into the JavaScript code:

MusicMaker.queueSound('Sounds/c4.m4a');

For more information on generators, read the generating code page on the developer site.

Most of your work so far has been in the Edit mode. Now you will update the Play mode to actually execute the custom code associated with each block.

The function handlePlay is already defined in scripts/main.js, but it's empty. Load the workspace content associated with the pressed button:

loadWorkspace(event.target);

Next, you need to generate the code out of that workspace, which you can do with a call to javascript.javascriptGenerator.workspaceToCode.

The user's code will consist of many MusicMaker.queueSound calls. At the end of our generated script, add a call to MusicMaker.play to play all the sounds added to the queue:

let code = javascript.javascriptGenerator.workspaceToCode(Blockly.getMainWorkspace());
code += 'MusicMaker.play();';

Finally, execute the script with the eval function. Wrap it in a try/catch so that any runtime errors are logged to the console, instead of failing quietly:

try {
  eval(code);
} catch (error) {
  console.log(error);
}

The full code

The end result should look like this:

function handlePlay(event) {
  loadWorkspace(event.target);
  let code = javascript.javascriptGenerator.workspaceToCode(Blockly.getMainWorkspace());
  code += 'MusicMaker.play();';
  try {
    eval(code);
  } catch (error) {
    console.log(error);
  }
}

A note on eval

Executing scripts with eval is not always the safest option - we use it here for simplicity. If you intend to run the user's blocks in production, check out the JS Interpreter project. This project is separate from Blockly, but was specifically written for Blockly.

Test it

Run the app and try it out! Edit one of the buttons to play a D4 sound 3 times:

image

Save and exit the edit mode. Now if you tap this button, you should hear the D4 sound played 3 times.

And with that, you're done with the Blockly codelab! If you'd like to continue playing with the app, we suggest adding or changing the available blocks. There are sample sound files in the sounds folder - try hooking them up to a new block!

For more documentation, visit the Blockly developer site.

Additionally, Blockly has an active developer forum. Please drop by and say hello. We're happy to answer any questions or give advice on best practices for building an app with Blockly. Feel free to show us your prototypes early; collectively we have a lot of experience and can offer hints which will save you time.