Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 18 additions & 1 deletion block-lexical-variables/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -83,13 +83,17 @@ 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 as demonstrated in the [Usage](#usage) section.

## Loops
### For
**Block type: 'controls_for'** - A block which enables a `for` loop
Expand Down Expand Up @@ -161,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
Expand Down
4 changes: 3 additions & 1 deletion block-lexical-variables/src/core.js
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand All @@ -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;
Expand Down
15 changes: 15 additions & 0 deletions block-lexical-variables/src/warningHandler.js
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,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<params.dropDowns.length; i++) {
const dropDown = block.getField(params.dropDowns[i]);
const dropDownList = dropDown.menuGenerator_();
Expand All @@ -138,9 +144,18 @@ 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
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.workspace.disableInvalidBlocks) {
block.setDisabledReason(false, Blockly.Msg.ERROR_SELECT_VALID_ITEM_FROM_DROPDOWN);
}
return false;
};
}
2 changes: 1 addition & 1 deletion block-lexical-variables/test/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

Expand Down
131 changes: 131 additions & 0 deletions block-lexical-variables/test/variable-get-set.mocha.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
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();
this.workspace.disableInvalidBlocks = true;
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(
'<xml xmlns="https://developers.google.com/blockly/xml">' +
' <block type="procedures_defnoreturn" id="procdef" x="165" y="-2391">' +
' <field name="NAME">do_something</field>' +
' <statement name="STACK">' +
' <block type="lexical_variable_set" id="a">' +
' <field name="VAR">global name</field>' +
' </block>' +
' </statement>' +
' </block>' +
'</xml>'
);

this.assertBlockEnabled(xml, false)
})

test('shouldn\'t be disabled if variable does exits', function() {
const xml = Blockly.utils.xml.textToDom(
'<xml xmlns="https://developers.google.com/blockly/xml">' +
' <block type="global_declaration" id="i/93m6|[C[h%qwFGxncO" x="141" y="-2266">' +
' <field name="NAME">name</field>' +
' </block>' +
' <block type="procedures_defnoreturn" id="@C.I~=@6)@mhE-+1}Xu^" x="148" y="-2191">' +
' <field name="NAME">do_something</field>' +
' <statement name="STACK">' +
' <block type="lexical_variable_set" id="a">' +
' <field name="VAR">global name</field>' +
' </block>' +
' </statement>' +
' </block>' +
'</xml>'
);

this.assertBlockEnabled(xml, true)
})

test('should be disabled for lexical out of scope', function() {
const xml = Blockly.utils.xml.textToDom('<xml>' +
' <block type="controls_if" id="target">' +
' <statement name="DO0">' +
' <block type="local_declaration_statement">' +
' <mutation>' +
' <localname name="a"></localname>' +
' <localname name="b"></localname>' +
' </mutation>' +
' <field name="VAR0">a</field>' +
' <field name="VAR1">b</field>' +
' <value name="DECL1">' +
' <block type="lexical_variable_get" id="a">' +
' <field name="VAR">b</field>' +
' </block>' +
' </value>' +
' <statement name="STACK">' +
' <block type="lexical_variable_set" id="b">' +
' <field name="VAR">a</field>' +
' </block>' +
' </statement>' +
' </block>' +
' </statement>' +
' </block>' +
'</xml>');
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(
'<xml xmlns="https://developers.google.com/blockly/xml">' +
' <block type="procedures_defnoreturn" id="procdef" x="165" y="-2391">' +
' <field name="NAME">do_something</field>' +
' <statement name="STACK">' +
' <block type="lexical_variable_set" id="a">' +
' <field name="VAR">global name</field>' +
' </block>' +
' </statement>' +
' </block>' +
'</xml>'
);

this.assertBlockEnabled(xml, true)
})
})
})