Skip to Content
docsBlockly Integration

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 blocks

Basic 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 forward
  • move_backward - Move harvester backward
  • turn_left - Turn 90° left
  • turn_right - Turn 90° right
  • turn_degrees - Turn specific angle

Harvester Operations

  • toggle_header - Turn header on/off
  • toggle_seeder - Turn seeder on/off
  • check_header_status - Get header state (boolean)
  • check_seeder_status - Get seeder state (boolean)

Sensors

  • get_crop_ahead - Check if crop is in front
  • get_tile_type - Get current tile type
  • get_yield_score - Get current yield

Logic & Control Blocks

Conditionals

  • if_then - Basic conditional
  • if_then_else - Conditional with alternative
  • if_crop_ahead - Specialized crop check

Loops

  • repeat_times - Fixed iteration loop
  • repeat_until - Condition-based loop
  • while_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

  1. Keep blocks simple - Each block should do one thing
  2. Use clear naming - Block names should be self-explanatory
  3. Provide tooltips - Help students understand block purpose
  4. Color-code categories - Consistent colors aid recognition
  5. Validate inputs - Set min/max values for number fields
  6. Test thoroughly - Ensure blocks work in all combinations
  7. 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); }); });

Resources

See Also