diff --git a/blocks/procedures.ts b/blocks/procedures.ts index 7f4a0ef5c..25fe240b8 100644 --- a/blocks/procedures.ts +++ b/blocks/procedures.ts @@ -921,10 +921,9 @@ const PROCEDURE_CALL_COMMON = { type: 'field_label', text: this.arguments_[i], }) as FieldLabel; - const input = this.appendValueInput('ARG' + i) + this.appendValueInput('ARG' + i) .setAlign(Align.RIGHT) .appendField(newField, 'ARGNAME' + i); - input.init(); } } // Remove deleted inputs. @@ -937,7 +936,6 @@ const PROCEDURE_CALL_COMMON = { if (this.arguments_.length) { if (!this.getField('WITH')) { topRow.appendField(Msg['PROCEDURES_CALL_BEFORE_PARAMS'], 'WITH'); - topRow.init(); } } else { if (this.getField('WITH')) { diff --git a/core/block.ts b/core/block.ts index becc8c91b..ea7b63a7a 100644 --- a/core/block.ts +++ b/core/block.ts @@ -191,6 +191,13 @@ export class Block implements IASTNodeLocation, IDeletable { */ private disposing = false; + /** + * Has this block been fully initialized? E.g. all fields initailized. + * + * @internal + */ + initialized = false; + private readonly xy_: Coordinate; isInFlyout: boolean; isInMutator: boolean; @@ -373,13 +380,11 @@ export class Block implements IASTNodeLocation, IDeletable { * change). */ initModel() { + if (this.initialized) return; for (const input of this.inputList) { - for (const field of input.fieldRow) { - if (field.initModel) { - field.initModel(); - } - } + input.initModel(); } + this.initialized = true; } /** diff --git a/core/block_svg.ts b/core/block_svg.ts index 5dcb1d4bd..d9f61dd6d 100644 --- a/core/block_svg.ts +++ b/core/block_svg.ts @@ -163,6 +163,9 @@ export class BlockSvg */ constructor(workspace: WorkspaceSvg, prototypeName: string, opt_id?: string) { super(workspace, prototypeName, opt_id); + if (!workspace.rendered) { + throw TypeError('Cannot create a rendered block in a headless workspace'); + } this.workspace = workspace; this.svgGroup_ = dom.createSvgElement(Svg.G, {}); @@ -189,10 +192,8 @@ export class BlockSvg * May be called more than once. */ initSvg() { - if (!this.workspace.rendered) { - throw TypeError('Workspace is headless.'); - } - for (let i = 0, input; (input = this.inputList[i]); i++) { + if (this.initialized) return; + for (const input of this.inputList) { input.init(); } for (const icon of this.getIcons()) { @@ -202,7 +203,7 @@ export class BlockSvg this.applyColour(); this.pathObject.updateMovable(this.isMovable()); const svg = this.getSvgRoot(); - if (!this.workspace.options.readOnly && !this.eventsInit_ && svg) { + if (!this.workspace.options.readOnly && svg) { browserEvents.conditionalBind( svg, 'pointerdown', @@ -210,11 +211,11 @@ export class BlockSvg this.onMouseDown_, ); } - this.eventsInit_ = true; if (!svg.parentNode) { this.workspace.getCanvas().appendChild(svg); } + this.initialized = true; } /** diff --git a/core/field.ts b/core/field.ts index 4ee4b1763..0a22797bf 100644 --- a/core/field.ts +++ b/core/field.ts @@ -314,6 +314,7 @@ export abstract class Field this.setTooltip(this.tooltip_); this.bindEvents_(); this.initModel(); + this.applyColour(); } /** diff --git a/core/inputs/input.ts b/core/inputs/input.ts index bcf8cab78..da7cccad5 100644 --- a/core/inputs/input.ts +++ b/core/inputs/input.ts @@ -103,10 +103,7 @@ export class Input { } field.setSourceBlock(this.sourceBlock); - if (this.sourceBlock.rendered) { - field.init(); - field.applyColour(); - } + if (this.sourceBlock.initialized) this.initField(field); field.name = opt_name; field.setVisible(this.isVisible()); @@ -270,11 +267,28 @@ export class Input { /** Initialize the fields on this input. */ init() { - if (!this.sourceBlock.rendered) { - return; // Headless blocks don't need fields initialized. + for (const field of this.fieldRow) { + field.init(); } - for (let i = 0; i < this.fieldRow.length; i++) { - this.fieldRow[i].init(); + } + + /** + * Initializes the fields on this input for a headless block. + * + * @internal + */ + public initModel() { + for (const field of this.fieldRow) { + field.initModel(); + } + } + + /** Initializes the given field. */ + private initField(field: Field) { + if (this.sourceBlock.rendered) { + field.init(); + } else { + field.initModel(); } } diff --git a/core/render_management.ts b/core/render_management.ts index 5aadc563c..c5e3d2af5 100644 --- a/core/render_management.ts +++ b/core/render_management.ts @@ -166,6 +166,7 @@ function shouldRenderRootBlock(block: BlockSvg): boolean { */ function renderBlock(block: BlockSvg) { if (!dirtyBlocks.has(block)) return; + if (!block.initialized) return; for (const child of block.getChildren(false)) { renderBlock(child); } diff --git a/tests/mocha/render_management_test.js b/tests/mocha/render_management_test.js index 971ed1667..9d368ef7b 100644 --- a/tests/mocha/render_management_test.js +++ b/tests/mocha/render_management_test.js @@ -33,6 +33,7 @@ suite('Render Management', function () { getRelativeToSurfaceXY: () => ({x: 0, y: 0}), updateComponentLocations: () => {}, bumpNeighbours: () => {}, + initialized: true, workspace: { resizeContents: () => {}, },