Skip to content

Behaviors & Code Generation

Definitions describe how blocks look; behaviors define what they do. The behaviors map pairs each block identifier with the function that generates its executable code:

import type { MorphicBehaviorMap } from "morphic-blocks";
export const behaviors: MorphicBehaviorMap = {
text_print(proxy) {
return `console.log(${proxy.inputs.TEXT ?? "undefined"});\n`;
},
};

Code generation is one-way and developer-controlled: whatever string your behavior returns is the code. There is no fixed target language — behaviors typically emit JavaScript because that’s what the browser can run, but the strings are yours.

Your generate function receives a proxy of the live block:

Property What it holds
blockId The Blockly block id
blockType The clean block identifier (e.g. text_print)
mode The active workspace mode
inputs Generated code of attached blocks, keyed by input name
fields Field values, keyed by field name
context Render context

proxy.inputs.TEXT is the already generated code of whatever block sits in the TEXT slot — generation recurses through the program for you.

A behavior can be more than a generate function. The long form:

export const behaviors: MorphicBehaviorMap = {
text_print: {
init(block, context) {
// one-time setup when the block is created
},
onViewApplied(block, context) {
// called after a mode/view is applied — attach *custom* Blockly fields
// here (dropdown/text/number/checkbox are declared in `fields` instead)
},
generate(proxy) {
return `console.log(${proxy.inputs.TEXT ?? "undefined"});\n`;
},
},
};
Hook When it runs
init Once, when the block is instantiated
onViewApplied After each mode application — attach custom fields (standard dropdown/text/number/checkbox go in fields)
generate During code generation

A bare function is shorthand for { generate }.

const js = engine.generateJavaScript();
// with block → code-position metadata (used by selection sync):
const { code, metadata } = engine.generateJavaScriptWithMetadata();
// execute in the page:
engine.runJavaScript();

Blocks encode grouping by nesting: a multiply block holding an add block is 2 * (3 + 4). Plain text loses that unless the brackets are written out, so the framework adds them — in the executable code and in the rendered text alike.

An operand is bracketed when two conditions hold: the operand block composes values of its own (it has value inputs), and the parent template composes several values. The second condition keeps single-slot templates clean:

Template Value slots Result
%1 %OP %2 2 2 * (3 + 4) — grouping preserved
print(%1) 1 print(2 * 3)
if %1: 1 if 10 == 20: — idiomatic Python

A unary operator is the exception: -%1 has a single slot, yet -(3 + 4) does need brackets. Write them into the template yourself — -(%1) — where the framework cannot infer the intent.

Two different things turn blocks into text — don’t confuse them:

  • Behaviors produce the executable code (generateJavaScript()).
  • Templates (code elements with %N substitution) produce what the codespace and preview display.

They are independent by design: what learners see can be Python while what runs is JavaScript. See Presets & Views for wiring views, and the Codespace guide for the editable text view.