From 4a4b50d83ef31b85ca8d18d40f4fe87915cb0464 Mon Sep 17 00:00:00 2001 From: TannerGabriel Date: Fri, 8 Aug 2025 17:36:30 +0200 Subject: [PATCH 1/5] Disable getters and setters with invalid value --- block-lexical-variables/src/warningHandler.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/block-lexical-variables/src/warningHandler.js b/block-lexical-variables/src/warningHandler.js index cefba47..05fc081 100644 --- a/block-lexical-variables/src/warningHandler.js +++ b/block-lexical-variables/src/warningHandler.js @@ -140,9 +140,15 @@ export class ErrorCheckers { if (!textInDropDown) { const errorMessage = Blockly.Msg.ERROR_SELECT_VALID_ITEM_FROM_DROPDOWN; block.workspace.getWarningHandler().setError(block, errorMessage); + // Disable the block when dropdown has invalid value + block.setEnabled(false); return true; } } + // Re-enable the block if all dropdowns are valid + if (!block.isEnabled()) { + block.setEnabled(true); + } return false; }; } From 2c3c686ccb877196b56e1db56730194ed7fb3351 Mon Sep 17 00:00:00 2001 From: TannerGabriel Date: Fri, 8 Aug 2025 17:54:17 +0200 Subject: [PATCH 2/5] Don't disable blocks while dragging out of the toolbox --- block-lexical-variables/src/warningHandler.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/block-lexical-variables/src/warningHandler.js b/block-lexical-variables/src/warningHandler.js index 05fc081..65ab24c 100644 --- a/block-lexical-variables/src/warningHandler.js +++ b/block-lexical-variables/src/warningHandler.js @@ -117,6 +117,12 @@ export class ErrorCheckers { if (block.workspace.isDragging && block.workspace.isDragging()) { return false; // wait until the user is done dragging to check validity. } + + // Don't disable blocks that are being dragged from toolbox or aren't fully rendered + if (block.isInFlyout || !block.workspace || block.workspace.isFlyout || !block.rendered) { + return false; + } + for (let i=0; i Date: Fri, 8 Aug 2025 22:55:34 +0200 Subject: [PATCH 3/5] Disable getter and setter unit tests --- .../test/variable-get-set.mocha.js | 112 ++++++++++++++++++ 1 file changed, 112 insertions(+) create mode 100644 block-lexical-variables/test/variable-get-set.mocha.js diff --git a/block-lexical-variables/test/variable-get-set.mocha.js b/block-lexical-variables/test/variable-get-set.mocha.js new file mode 100644 index 0000000..06f2ce0 --- /dev/null +++ b/block-lexical-variables/test/variable-get-set.mocha.js @@ -0,0 +1,112 @@ +import * as Blockly from 'blockly/core'; +import * as libraryBlocks from 'blockly/blocks'; + +import '../src/msg'; +import '../src/utilities'; +import '../src/workspace'; +import '../src/procedure_utils'; +import '../src/fields/flydown'; +import '../src/fields/field_flydown'; +import '../src/fields/field_global_flydown'; +import '../src/fields/field_nocheck_dropdown'; +import '../src/fields/field_parameter_flydown'; +import '../src/fields/field_procedurename'; +import '../src/blocks/lexical-variables'; +import '../src/blocks/controls'; +import '../src/blocks/variable-get-set.js'; +import '../src/procedure_database'; +import '../src/blocks/procedures'; +import '../src/generators/controls'; +import '../src/generators/procedures'; +import '../src/generators/lexical-variables'; + +import chai from 'chai'; + +suite ('VariableGetSet', function() { + setup(function() { + this.workspace = new Blockly.Workspace(); + Blockly.common.setMainWorkspace(this.workspace); + }); + teardown(function() { + delete this.workspace; + }) + + suite('disable getter and setter if input is invalid', function() { + setup(function() { + this.assertBlockEnabled = function(xml, enabled, blockId = 'a') { + Blockly.Xml.domToWorkspace(xml, this.workspace); + const block = this.workspace.getBlockById(blockId); + block.rendered = true; + block.onchange(() => {}) + chai.assert.equal(block.isEnabled(), enabled); + } + }) + + test('should be disabled if variable doesn\'t exits', function() { + const xml = Blockly.utils.xml.textToDom( + '' + + ' ' + + ' do_something' + + ' ' + + ' ' + + ' global name' + + ' ' + + ' ' + + ' ' + + '' + ); + + this.assertBlockEnabled(xml, false) + }) + + test('shouldn\'t be disabled if variable does exits', function() { + const xml = Blockly.utils.xml.textToDom( + '' + + ' ' + + ' name' + + ' ' + + ' ' + + ' do_something' + + ' ' + + ' ' + + ' global name' + + ' ' + + ' ' + + ' ' + + '' + ); + + this.assertBlockEnabled(xml, true) + }) + + test('should be disabled for lexical out of scope', function() { + const xml = Blockly.utils.xml.textToDom('' + + ' ' + + ' ' + + ' ' + + ' ' + + ' ' + + ' ' + + ' ' + + ' a' + + ' b' + + ' ' + + ' ' + + ' b' + + ' ' + + ' ' + + ' ' + + ' ' + + ' a' + + ' ' + + ' ' + + ' ' + + ' ' + + ' ' + + ''); + this.assertBlockEnabled(xml, false) + this.assertBlockEnabled(xml, true, 'b') + }) + }) +}) + From be720fdf08ee658ee6060679aaadfdb28607302f Mon Sep 17 00:00:00 2001 From: TannerGabriel Date: Wed, 10 Sep 2025 08:25:10 +0200 Subject: [PATCH 4/5] Make disabling block configurable --- block-lexical-variables/README.md | 10 +++++++++- block-lexical-variables/src/core.js | 4 +++- block-lexical-variables/src/warningHandler.js | 9 ++++++--- block-lexical-variables/test/index.js | 2 +- .../test/variable-get-set.mocha.js | 19 +++++++++++++++++++ 5 files changed, 38 insertions(+), 6 deletions(-) diff --git a/block-lexical-variables/README.md b/block-lexical-variables/README.md index cc85493..9851ffe 100644 --- a/block-lexical-variables/README.md +++ b/block-lexical-variables/README.md @@ -83,13 +83,21 @@ in scope for that getter according to which blocks it is within. ![A picture of a setter block with a dropdown](readme-media/set-with-dropdown.png "Setter with dropdown") ![A picture of a setter block within another block](readme-media/set-within-scope.png "Setter with dropdown") -###Getter +### Getter **Block type: 'lexical_variable_get'** - Exactly analogous to the setter block. ![A picture of a getter block](readme-media/get.png "Getter") ![A picture of a getter block with a dropdown](readme-media/get-with-dropdown.png "Getter with dropdown") ![A picture of a getter block within another block](readme-media/get-within-scope.png "Getter with dropdown") +### Disabling invalid getter/setter blocks + +By default, getter and setter blocks remain enabled even if their selected variable is out of scope or doesn’t exist. You can change this behavior so that invalid blocks are automatically disabled by passing the `disableInvalidBlocks` option when initializing the plugin: + +```js +LexicalVariablesPlugin.init(workspace, { disableInvalidBlocks: true }); +```` + ## Loops ### For **Block type: 'controls_for'** - A block which enables a `for` loop diff --git a/block-lexical-variables/src/core.js b/block-lexical-variables/src/core.js index 1a58dad..7c39377 100644 --- a/block-lexical-variables/src/core.js +++ b/block-lexical-variables/src/core.js @@ -30,8 +30,9 @@ export class LexicalVariablesPlugin { /** * @param workspace + * @param options */ - static init(workspace) { + static init(workspace, options) { // TODO(ewpatton): We need to make sure this is reentrant. const rendererName = workspace.getRenderer().getClassName(); const themeName = workspace.getTheme().getClassName(); @@ -55,6 +56,7 @@ export class LexicalVariablesPlugin { workspace.svgBubbleCanvas_); flydown.init(workspace); flydown.autoClose = true; // Flydown closes after selecting a block + workspace.disableInvalidBlocks = options?.disableInvalidBlocks || false; } static Flydown = Flydown; diff --git a/block-lexical-variables/src/warningHandler.js b/block-lexical-variables/src/warningHandler.js index 934ce1a..38c9add 100644 --- a/block-lexical-variables/src/warningHandler.js +++ b/block-lexical-variables/src/warningHandler.js @@ -145,13 +145,16 @@ export class ErrorCheckers { const errorMessage = Blockly.Msg.ERROR_SELECT_VALID_ITEM_FROM_DROPDOWN; block.workspace.getWarningHandler().setError(block, errorMessage); // Disable the block when dropdown has invalid value - block.setEnabled(false); + if (block.workspace.disableInvalidBlocks) { + block.setDisabledReason(true, Blockly.Msg.ERROR_SELECT_VALID_ITEM_FROM_DROPDOWN); + } + return true; } } // Re-enable the block if all dropdowns are valid - if (!block.isEnabled()) { - block.setEnabled(true); + if (!block.isEnabled() && block.workspace.disableInvalidBlocks) { + block.setDisabledReason(false, Blockly.Msg.ERROR_SELECT_VALID_ITEM_FROM_DROPDOWN); } return false; }; diff --git a/block-lexical-variables/test/index.js b/block-lexical-variables/test/index.js index 1c1dc26..95d4cd8 100644 --- a/block-lexical-variables/test/index.js +++ b/block-lexical-variables/test/index.js @@ -39,7 +39,7 @@ const allBlocks = [ */ function createWorkspace(blocklyDiv, options) { const workspace = Blockly.inject(blocklyDiv, options); - LexicalVariablesPlugin.init(workspace); + LexicalVariablesPlugin.init(workspace, {disableInvalidBlocks: true}); return workspace; } diff --git a/block-lexical-variables/test/variable-get-set.mocha.js b/block-lexical-variables/test/variable-get-set.mocha.js index 06f2ce0..ba01f4f 100644 --- a/block-lexical-variables/test/variable-get-set.mocha.js +++ b/block-lexical-variables/test/variable-get-set.mocha.js @@ -25,6 +25,7 @@ import chai from 'chai'; suite ('VariableGetSet', function() { setup(function() { this.workspace = new Blockly.Workspace(); + this.workspace.disableInvalidBlocks = true; Blockly.common.setMainWorkspace(this.workspace); }); teardown(function() { @@ -107,6 +108,24 @@ suite ('VariableGetSet', function() { this.assertBlockEnabled(xml, false) this.assertBlockEnabled(xml, true, 'b') }) + + test('should not be disabled if disableInvalidBlocks = false', function() { + this.workspace.disableInvalidBlocks = false; + const xml = Blockly.utils.xml.textToDom( + '' + + ' ' + + ' do_something' + + ' ' + + ' ' + + ' global name' + + ' ' + + ' ' + + ' ' + + '' + ); + + this.assertBlockEnabled(xml, true) + }) }) }) From b43fa549f5b20ef7588e2df49d5f7f07a9de7fbd Mon Sep 17 00:00:00 2001 From: TannerGabriel Date: Fri, 12 Sep 2025 11:43:40 +0200 Subject: [PATCH 5/5] Move init option to the usage section --- block-lexical-variables/README.md | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/block-lexical-variables/README.md b/block-lexical-variables/README.md index 9851ffe..135fdb2 100644 --- a/block-lexical-variables/README.md +++ b/block-lexical-variables/README.md @@ -92,11 +92,7 @@ in scope for that getter according to which blocks it is within. ### Disabling invalid getter/setter blocks -By default, getter and setter blocks remain enabled even if their selected variable is out of scope or doesn’t exist. You can change this behavior so that invalid blocks are automatically disabled by passing the `disableInvalidBlocks` option when initializing the plugin: - -```js -LexicalVariablesPlugin.init(workspace, { disableInvalidBlocks: true }); -```` +By default, getter and setter blocks remain enabled even if their selected variable is out of scope or doesn’t exist. You can change this behavior so that invalid blocks are automatically disabled by passing the `disableInvalidBlocks` option when initializing the plugin as demonstrated in the [Usage](#usage) section. ## Loops ### For @@ -169,6 +165,19 @@ const workspace = Blockly.inject(...); // Load lexical variable plugin LexicalVariablesPlugin.init(workspace); ``` + +You can also pass an options object to customize the plugin’s behavior: + +```js +LexicalVariablesPlugin.init(workspace, { + disableInvalidBlocks: true, +}); +```` + +**Available options:** +- `disableInvalidBlocks` *(boolean, default: false)* + Automatically disable variable getter/setter blocks when they reference a variable that is out of scope or doesn’t exist. + Note that unlike with standard Blockly, you should **not** use a custom toolbox category for your variables, as this would interfere with the way that variables are declared and used with this plugin. Just create an ordinary Variables category, if you want, and