Last Updated: 3/9/2026
Blockly Integration Guide
This guide explains how Blockly is integrated into the Agricultural Microworlds application and how to create custom blocks.
Overview
Blockly is a visual programming library that allows students to write code by dragging and connecting blocks. Our application uses Blockly 12.3.1 to provide an intuitive programming interface for K-12 students.
Architecture
Component Structure
BlocklySection/
├── BlocklyWorkspaceContainer.jsx # Main workspace component
├── BlockLibrary.jsx # Available blocks panel
└── CodePlayground.jsx # Workspace where blocks connect
SetUpCustomBlocks/
├── harvesterBlocks.js # Combine harvester blocks
├── movementBlocks.js # Movement control blocks
├── logicBlocks.js # Conditional blocks
└── loopBlocks.js # Iteration blocksBasic Integration
Workspace Setup
import Blockly from 'blockly';
import { createRef } from 'react';
class BlocklyWorkspaceContainer extends Component {
constructor(props) {
super(props);
this.workspaceRef = createRef();
this.state = { workspace: null };
}
componentDidMount() {
const workspace = Blockly.inject(this.workspaceRef.current, {
toolbox: this.getToolboxConfig(),
grid: { spacing: 20, length: 3, colour: '#ccc' },
zoom: {
controls: true,
wheel: true,
startScale: 1.0,
maxScale: 3,
minScale: 0.3
},
trashcan: true
});
this.setState({ workspace });
this.props.onWorkspaceReady(workspace);
}
}Creating Custom Blocks
Block Definition
Custom blocks are defined in the SetUpCustomBlocks/ directory.
Example: Move Forward Block
import Blockly from 'blockly';
// Define block structure
Blockly.Blocks['move_forward'] = {
init: function() {
this.jsonInit({
"type": "move_forward",
"message0": "move forward %1 spaces",
"args0": [
{
"type": "field_number",
"name": "DISTANCE",
"value": 1,
"min": 1,
"max": 10
}
],
"previousStatement": null,
"nextStatement": null,
"colour": 160,
"tooltip": "Move the harvester forward",
"helpUrl": ""
});
}
};
// Define code generation
Blockly.JavaScript['move_forward'] = function(block) {
const distance = block.getFieldValue('DISTANCE');
return `moveForward(${distance});\n`;
};Block Types
1. Statement Blocks (Commands)
{
"previousStatement": null, // Can connect above
"nextStatement": null, // Can connect below
"colour": 160 // Green for movement
}2. Value Blocks (Return values)
{
"output": "Number", // Returns a number
"colour": 230 // Blue for values
}3. Boolean Blocks (Conditions)
{
"output": "Boolean", // Returns true/false
"colour": 210 // Purple for logic
}4. Container Blocks (Loops, conditionals)
{
"message0": "repeat %1 times",
"args0": [
{ "type": "field_number", "name": "TIMES", "value": 10 }
],
"message1": "do %1",
"args1": [
{ "type": "input_statement", "name": "DO" } // Nested blocks
],
"previousStatement": null,
"nextStatement": null,
"colour": 120 // Orange for control
}Agricultural Microworlds Blocks
Harvester Control Blocks
Movement
move_forward- Move harvester forwardmove_backward- Move harvester backwardturn_left- Turn 90° leftturn_right- Turn 90° rightturn_degrees- Turn specific angle
Harvester Operations
toggle_header- Turn header on/offtoggle_seeder- Turn seeder on/offcheck_header_status- Get header state (boolean)check_seeder_status- Get seeder state (boolean)
Sensors
get_crop_ahead- Check if crop is in frontget_tile_type- Get current tile typeget_yield_score- Get current yield
Logic & Control Blocks
Conditionals
if_then- Basic conditionalif_then_else- Conditional with alternativeif_crop_ahead- Specialized crop check
Loops
repeat_times- Fixed iteration looprepeat_until- Condition-based loopwhile_loop- While condition is true
Grade-Level Restrictions
const blocksByGrade = {
'K-5': [
'move_forward',
'turn_left',
'turn_right',
'toggle_header',
'repeat_times'
],
'6-8': [
// K-5 blocks plus:
'move_backward',
'turn_degrees',
'if_then',
'repeat_until',
'get_crop_ahead'
],
'9-12': [
// All blocks plus:
'while_loop',
'custom_functions',
'variables',
'advanced_sensors'
]
};Code Execution
Generating JavaScript
import Blockly from 'blockly';
import { javascriptGenerator } from 'blockly/javascript';
function generateCode(workspace) {
// Generate JavaScript from blocks
const code = javascriptGenerator.workspaceToCode(workspace);
return code;
}Executing Code
function executeBlocks(workspace, simulationState) {
const code = generateCode(workspace);
// Create execution context with simulation API
const context = {
moveForward: (distance) => simulationState.moveHarvester(distance, 0),
turnLeft: () => simulationState.rotateHarvester(-90),
toggleHeader: () => simulationState.toggleHeader(),
// ... other simulation methods
};
// Execute in isolated scope
try {
const func = new Function(...Object.keys(context), code);
func(...Object.values(context));
} catch (error) {
console.error('Execution error:', error);
// Show error to student
}
}Toolbox Configuration
Basic Toolbox
<xml id="toolbox" style="display: none">
<category name="Movement" colour="160">
<block type="move_forward"></block>
<block type="turn_left"></block>
<block type="turn_right"></block>
</category>
<category name="Harvester" colour="120">
<block type="toggle_header"></block>
<block type="toggle_seeder"></block>
</category>
<category name="Logic" colour="210">
<block type="if_then"></block>
<block type="if_then_else"></block>
</category>
<category name="Loops" colour="120">
<block type="repeat_times"></block>
<block type="repeat_until"></block>
</category>
</xml>Dynamic Toolbox (Lesson-based)
function getToolboxForLesson(lessonId, gradeLevel) {
const allowedBlocks = lessons[lessonId].blocks;
const gradeBlocks = blocksByGrade[gradeLevel];
// Intersect lesson blocks with grade-appropriate blocks
const blocks = allowedBlocks.filter(b => gradeBlocks.includes(b));
return generateToolboxXML(blocks);
}Styling
Block Colors
const BLOCK_COLORS = {
movement: 160, // Green
harvester: 90, // Dark green
logic: 210, // Purple
loops: 120, // Orange
sensors: 230, // Blue
variables: 330 // Red
};Custom Theme
const agriculturalTheme = Blockly.Theme.defineTheme('agricultural', {
base: Blockly.Themes.Classic,
blockStyles: {
movement_blocks: {
colourPrimary: '#4CAF50',
colourSecondary: '#66BB6A',
colourTertiary: '#388E3C'
},
harvester_blocks: {
colourPrimary: '#8BC34A',
colourSecondary: '#9CCC65',
colourTertiary: '#689F38'
}
},
categoryStyles: {
movement_category: { colour: '#4CAF50' },
harvester_category: { colour: '#8BC34A' }
}
});Accessibility
Keyboard Navigation
Blockly.navigation.enableKeyboardAccessibility();
// Custom keyboard shortcuts
workspace.addChangeListener((event) => {
if (event.type === Blockly.Events.KEY_DOWN) {
if (event.key === 'r') {
// Run code
executeBlocks(workspace, simulationState);
}
}
});Screen Reader Support
// Add ARIA labels to blocks
Blockly.Blocks['move_forward'].init = function() {
// ... existing init
this.setTooltip('Move the harvester forward');
this.setHelpUrl('/docs/blocks/move-forward');
};Best Practices
- Keep blocks simple - Each block should do one thing
- Use clear naming - Block names should be self-explanatory
- Provide tooltips - Help students understand block purpose
- Color-code categories - Consistent colors aid recognition
- Validate inputs - Set min/max values for number fields
- Test thoroughly - Ensure blocks work in all combinations
- Document blocks - Include help URLs and examples
Testing Blocks
import { expect } from '@jest/globals';
describe('move_forward block', () => {
let workspace;
beforeEach(() => {
workspace = new Blockly.Workspace();
});
test('generates correct code', () => {
const block = workspace.newBlock('move_forward');
block.setFieldValue(5, 'DISTANCE');
const code = javascriptGenerator.blockToCode(block);
expect(code).toBe('moveForward(5);\n');
});
test('respects min/max values', () => {
const block = workspace.newBlock('move_forward');
const field = block.getField('DISTANCE');
expect(field.getMin()).toBe(1);
expect(field.getMax()).toBe(10);
});
});