Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
68 changes: 68 additions & 0 deletions packages/docs/docs/codelabs/custom-mutator/code-generation.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
---
description: Generating code for the mutator.
---

import Image from '@site/src/components/Image';

# Build a custom mutator

## 7. Generating code

Now, it's time to revisit the [block-code generator](https://docs.blockly.com/guides/create-custom-blocks/code-generation/block-code/)
in `javascript.js`. Since the sample app displays JavaScript code, you'll make a
generator for JavaScript.

### Update the code generator

Update the `resizable_list` generator in `javascript.js` to contain the
following code:

```js
forBlock['resizable_list'] = function (block, generator) {
// Create a list with any number of elements of any type.
const elements = new Array(block.itemCount);
for (let i = 0; i < block.itemCount; i++) {
elements[i] = generator.valueToCode(block, 'ADD' + i, Order.NONE) || 'null';
}
const code = '[' + elements.join(', ') + ']';
return [code, Order.ATOMIC];
};
```

This code generator converts each input to code, then comma-separates the list
and joins them all between two brackets. This is how a block like this:

<Image
src="/images/codelabs/custom-mutator/code_block.png"
alt="A list block with three inputs: the first is the integer 123, the second is empty, the third is a string that says 'Hello, world!'"
className="codelabImage"
/>

Generates this code:

```js
[123, null, 'Hello, world!'];
```

### Test it

Test your mutator by dragging it into the workspace, right clicking, and
adding an input. You may notice something strange. Although the block generates
code when it first enters the workspace, adding a new input does not update the
code.

Now try moving the list block. The code window will be updated.

### Why doesn't the code update immediately?

In the sample app, a function called `runCode()` handles generating the code
for the code window. A [change listener](https://docs.blockly.com/guides/configure/events/#listen-to-events-from-the-workspace)
in `index.js` calls `runCode()` when certain types of events are detected.

This means that when blocks are changed, moved, or added, an event is fired and
the code is regenerated. However, the new mutator does not fire any events, so adding
or removing an input is not enough to cause the code to be regenerated. In order for the code
to be regenerated, a different event has to occur (like moving the block).

The next step will address this issue by adding a function to the mutator that
fires an event when the number of inputs are changed.
31 changes: 31 additions & 0 deletions packages/docs/docs/codelabs/custom-mutator/codelab-overview.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
---
pagination_prev: null
description: Overview of the "Build a custom mutator" codelab.
---

import Image from '@site/src/components/Image';

# Build a custom mutator

## 1. Codelab overview

### What you'll learn
- What mutators are and when to use them
- How to create a custom mutator
- How to use context menus to create a custom mutator UI
- When and how to fire events during a mutation

### What you'll build
A custom list mutator that uses the context menu to add and remove items.

<Image
src="/images/codelabs/custom-mutator/finished_block.png"
alt="Screenshot of the block built in this codelab. There are multiple inputs to the block, with a context menu open that shows options for 'Add Item' and 'Remove Item'"
className="codelabImage"
/>

### What you'll need
- Basic understanding of Blockly blocks, toolboxes, and workspaces.
- Understanding of context menus in Blockly.
- NPM installed ([instructions](https://docs.npmjs.com/downloading-and-installing-node-js-and-npm)).
- Comfort using the command line/terminal.
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
# Blockly Sample App

## Purpose

This app illustrates how to use Blockly together with common programming tools like node/npm, webpack, typescript, eslint, and others. You can use it as the starting point for your own application and modify it as much as you'd like. It contains basic infrastructure for running, building, testing, etc. that you can use even if you don't understand how to configure the related tool yet. When your needs outgrow the functionality provided here, you can replace the provided configuration or tool with your own.

## Quick Start

1. [Install](https://docs.npmjs.com/downloading-and-installing-node-js-and-npm) npm if you haven't before.
2. Run [`npx @blockly/create-package app <application-name>`](https://www.npmjs.com/package/@blockly/create-package) to clone this application to your own machine.
3. Run `npm install` to install the required dependencies.
4. Run `npm run start` to run the development server and see the app in action.
5. If you make any changes to the source code, just refresh the browser while the server is running to see them.

## Tooling

The application uses many of the same tools that the Blockly team uses to develop Blockly itself. Following is a brief overview, and you can read more about them on our [developer site](https://docs.blockly.com/guides/contribute/core/development_tools/).

- Structure: The application is built as an npm package. You can use npm to manage the dependencies of the application.
- Modules: ES6 modules to handle imports to/exports from other files.
- Building/bundling: Webpack to build the source code and bundle it into one file for serving.
- Development server: webpack-dev-server to run locally while in development.
- Testing: Mocha to run unit tests.
- Linting: Eslint to lint the code and ensure it conforms with a standard style.
- UI Framework: Does not use a framework. For more complex applications, you may wish to integrate a UI framework like React or Angular.

You can disable, reconfigure, or replace any of these tools at any time, but they are preconfigured to get you started developing your Blockly application quickly.

## Structure

- `package.json` contains basic information about the app. This is where the scripts to run, build, etc. are listed.
- `package-lock.json` is used by npm to manage dependencies
- `webpack.config.js` is the configuration for webpack. This handles bundling the application and running our development server.
- `src/` contains the rest of the source code.
- `dist/` contains the packaged output (that you could host on a server, for example). This is ignored by git and will only appear after you run `npm run build` or `npm run start`.

### Source Code

- `index.html` contains the skeleton HTML for the page. This file is modified during the build to import the bundled source code output by webpack.
- `index.js` is the entry point of the app. It configures Blockly and sets up the page to show the blocks, the generated code, and the output of running the code in JavaScript.
- `serialization.js` has code to save and load the workspace using the browser's local storage. This is how your workspace is saved even after refreshing or leaving the page. You could replace this with code that saves the user's data to a cloud database instead.
- `toolbox.js` contains the toolbox definition for the app. The current toolbox contains nearly every block that Blockly provides out of the box. You probably want to replace this definition with your own toolbox that uses your custom blocks and only includes the default blocks that are relevant to your application.
- `blocks/text.js` has code for a custom text block, just as an example of creating your own blocks. You probably want to delete this block, and add your own blocks in this directory.
- `generators/javascript.js` contains the JavaScript generator for the custom text block. You'll need to include block generators for any custom blocks you create, in whatever programming language(s) your application will use.

## Serving

To run your app locally, run `npm run start` to run the development server. This mode generates source maps and ingests the source maps created by Blockly, so that you can debug using unminified code.

To deploy your app so that others can use it, run `npm run build` to run a production build. This will bundle your code and minify it to reduce its size. You can then host the contents of the `dist` directory on a web server of your choosing. If you're just getting started, try using [GitHub Pages](https://pages.github.com/).
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
{
"name": "walkthrough-mutator-codelab",
"version": "1.0.0",
"description": "A sample app using Blockly",
"main": "index.js",
"private": true,
"scripts": {
"test": "echo \"Warning: no test specified\" && exit 0",
"build": "webpack --mode production",
"start": "webpack serve --open --mode development"
},
"keywords": [
"blockly"
],
"author": "",
"license": "Apache-2.0",
"devDependencies": {
"css-loader": "^7.1.4",
"html-webpack-plugin": "^5.6.7",
"source-map-loader": "^5.0.0",
"style-loader": "^4.0.0",
"webpack": "^5.107.2",
"webpack-cli": "^7.0.3",
"webpack-dev-server": "^6.0.0"
},
"dependencies": {
"blockly": "^13.3.0"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/**
* @license
* Copyright 2026 Raspberry Pi Foundation
* SPDX-License-Identifier: Apache-2.0
*/

import * as Blockly from 'blockly/core';

const resizableListBlock = {
type: 'resizable_list',
message0: 'resizable list with %1',
args0: [
{
type: 'input_value',
name: 'ADD0',
},
],
output: null,
style: 'list_blocks',
mutator: 'list_mutator',
tooltip: '',
helpUrl: '',
};

// Create the block definitions
export const mutatorBlocks = Blockly.common.createBlockDefinitionsFromJsonArray(
[resizableListBlock],
);
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/**
* @license
* Copyright 2026 Raspberry Pi Foundation
* SPDX-License-Identifier: Apache-2.0
*/

import * as Blockly from 'blockly/core';

// Create a custom block called 'add_text' that adds
// text to the output div on the sample app.
// This is just an example and you should replace this with your
// own custom blocks.
const addText = {
type: 'add_text',
message0: 'Add text %1',
args0: [
{
type: 'input_value',
name: 'TEXT',
check: 'String',
},
],
previousStatement: null,
nextStatement: null,
colour: 160,
tooltip: '',
helpUrl: '',
};

// Create the block definitions for the JSON-only blocks.
// This does not register their definitions with Blockly.
// This file has no side effects!
export const blocks = Blockly.common.createBlockDefinitionsFromJsonArray([
addText,
]);
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
/**
* @license
* Copyright 2026 Raspberry Pi Foundation
* SPDX-License-Identifier: Apache-2.0
*/

import { Order } from 'blockly/javascript';

// Export all the code generators for our custom blocks,
// but don't register them with Blockly yet.
// This file has no side effects!
export const forBlock = Object.create(null);

forBlock['add_text'] = function (block, generator) {
const text = generator.valueToCode(block, 'TEXT', Order.NONE) || "''";
const addText = generator.provideFunction_(
'addText',
`function ${generator.FUNCTION_NAME_PLACEHOLDER_}(text) {

// Add text to the output area.
const outputDiv = document.getElementById('output');
const textEl = document.createElement('p');
textEl.innerText = text;
outputDiv.appendChild(textEl);
}`,
);
// Generate the function call for this block.
const code = `${addText}(${text});\n`;
return code;
};

forBlock['resizable_list'] = function (block, generator) {
// Create a list with any number of elements of any type.
const elements = new Array(block.itemCount);
for (let i = 0; i < block.itemCount; i++) {
elements[i] = generator.valueToCode(block, 'ADD' + i, Order.NONE) || 'null';
}
const code = '[' + elements.join(', ') + ']';
return [code, Order.ATOMIC];
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
body {
margin: 0;
max-width: 100vw;
}

pre,
code {
overflow: auto;
}

#pageContainer {
display: flex;
width: 100%;
max-width: 100vw;
height: 100vh;
}

#blocklyDiv {
flex-basis: 100%;
height: 100%;
min-width: 600px;
}

#outputPane {
display: flex;
flex-direction: column;
width: 400px;
flex: 0 0 400px;
overflow: auto;
margin: 1rem;
}

#generatedCode {
height: 50%;
background-color: rgb(247, 240, 228);
}

#output {
height: 50%;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<!doctype html>
<html>
<head>
<meta charset="utf-8" />
<title>Blockly Sample App</title>
</head>
<body>
<div id="pageContainer">
<div id="outputPane">
<pre id="generatedCode"><code></code></pre>
<div id="output"></div>
</div>
<div id="blocklyDiv"></div>
</div>
</body>
</html>
Loading
Loading