# Node Patterns Reference How to create and modify nodes in OpenNoodl. ## Node Types There are two main types of nodes: 1. **Runtime Nodes** (`noodl-runtime`) - Logic, data, utilities 2. **Visual Nodes** (`noodl-viewer-react`) - React components for UI ## Basic Node Structure ### Runtime Node (JavaScript) Location: `packages/noodl-runtime/src/nodes/` ```javascript 'use strict'; const MyNode = { // === METADATA === name: 'My.Custom.Node', // Unique identifier displayName: 'My Custom Node', // Shown in UI category: 'Custom', // Node picker category color: 'data', // Node color theme docs: 'https://docs.example.com', // Documentation link // === INITIALIZATION === initialize() { // Called when node is created this._internal.myValue = ''; this._internal.callbacks = []; }, // === INPUTS === inputs: { // Simple input textInput: { type: 'string', displayName: 'Text Input', group: 'General', default: '', set(value) { this._internal.textInput = value; this.flagOutputDirty('result'); } }, // Number with validation numberInput: { type: 'number', displayName: 'Number', group: 'General', default: 0, set(value) { if (typeof value !== 'number') return; this._internal.numberInput = value; this.flagOutputDirty('result'); } }, // Signal input (trigger) doAction: { type: 'signal', displayName: 'Do Action', group: 'Actions', valueChangedToTrue() { // Called when signal received this.performAction(); } }, // Boolean toggle enabled: { type: 'boolean', displayName: 'Enabled', group: 'General', default: true, set(value) { this._internal.enabled = value; } }, // Dropdown/enum mode: { type: { name: 'enum', enums: [ { value: 'mode1', label: 'Mode 1' }, { value: 'mode2', label: 'Mode 2' } ] }, displayName: 'Mode', group: 'General', default: 'mode1', set(value) { this._internal.mode = value; } } }, // === OUTPUTS === outputs: { // Value output result: { type: 'string', displayName: 'Result', group: 'General', getter() { return this._internal.result; } }, // Signal output completed: { type: 'signal', displayName: 'Completed', group: 'Events' }, // Error output error: { type: 'string', displayName: 'Error', group: 'Error', getter() { return this._internal.error; } } }, // === METHODS === methods: { performAction() { if (!this._internal.enabled) return; try { // Do something this._internal.result = 'Success'; this.flagOutputDirty('result'); this.sendSignalOnOutput('completed'); } catch (e) { this._internal.error = e.message; this.flagOutputDirty('error'); } }, // Called when node is deleted _onNodeDeleted() { // Cleanup this._internal.callbacks = []; } }, // === INSPECTOR (Debug Panel) === getInspectInfo() { return { type: 'text', value: `Current: ${this._internal.result}` }; } }; module.exports = { node: MyNode }; ``` ### Visual Node (React) Location: `packages/noodl-viewer-react/src/nodes/` ```javascript 'use strict'; const { Node } = require('@noodl/noodl-runtime'); const MyVisualNode = { name: 'My.Visual.Node', displayName: 'My Visual Node', category: 'UI Elements', // Visual nodes need these allowChildren: true, // Can have child nodes allowChildrenWithCategory: ['UI Elements'], // Restrict child types getReactComponent() { return MyReactComponent; }, // Frame updates for animations frame: { // Called every frame if registered update(context) { // Animation logic } }, inputs: { // Standard style inputs backgroundColor: { type: 'color', displayName: 'Background Color', group: 'Style', default: 'transparent', set(value) { this.props.style.backgroundColor = value; this.forceUpdate(); } }, // Dimension with units width: { type: { name: 'number', units: ['px', '%', 'vw'], defaultUnit: 'px' }, displayName: 'Width', group: 'Dimensions', set(value) { this.props.style.width = value.value + value.unit; this.forceUpdate(); } } }, outputs: { // DOM event outputs onClick: { type: 'signal', displayName: 'Click', group: 'Events' } }, methods: { // Called when mounted didMount() { // Setup }, // Called when unmounted willUnmount() { // Cleanup } } }; // React component function MyReactComponent(props) { const handleClick = () => { props.noodlNode.sendSignalOnOutput('onClick'); }; return (