Updated field dropdown causing issue in serialization and deserialization

97 views
Skip to first unread message

Kiran Batchu

unread,
May 20, 2024, 9:36:41 PM5/20/24
to Blockly
Hello,
I am running into an issue with deserializing an updated field dropdown. So I have two blocks as in the image below. In the green block, I have two field dropdowns 1) one next to the word 'Where' ie. 'ROUTES' - field name = "COLUMNS_FIELD" and 2) the one next to the word 'in' i.e. nyc_subway_stations'. When the user changes the selection in the second dropdown, the options get updated in the first dropdown. Everything works fine in the UI workspace. 
The issue is when I change the selection in the second dropdown and then save this using 'Blockly.serialization.workspaces.save()' and then load it again using 'Blockly.serialization.workspaces.load()', I get an error message 'Cannot set the dropdown's value to an unavailable option. Block type: where_comparison_block, Field name: COLUMNS_FIELD, Value: ROUTES'
Blockly_screenshot.PNG
I have this validator function in the green block's 'jsonInit()' function 
  const datasetDropdown = this.getField("DATASET");
      const columnsInput = this.getInput("COLUMNS_INPUT");
      let datasetColumnsList;

      // ? Add a change event listener to the Dataset dropdown field
      datasetDropdown.setValidator(function (newDatasetName) {        
        columnsInput.removeField("COLUMNS_FIELD");
        columnsInput.appendField(
          new Blockly.FieldDropdown(function () {
            datasetColumnsList = getColumnsListOptions(newDatasetName);
            return datasetColumnsList;
          }),
          "COLUMNS_FIELD"
        );

        const columnsField = this.getSourceBlock().getField("COLUMNS_FIELD");      
        columnsField.setValue(datasetColumnsList[0][0]);
      });
    };

    // ? Re-render the workspace
    Blockly.getMainWorkspace().render();
I am setting the value of the first dropdown after the second dropdown selection is changed. Where am I going wrong?

PS: Ideally, instead of removing and adding a new field dropdown with the same name with the changed options, I would like to just update the options in the existing dropdown but I could not find documentation on it.

Thanks,
Kiran

Kiran Batchu

unread,
May 21, 2024, 8:43:52 AM5/21/24
to Blockly
I am guessing since the second dropdown (lower order) controls the options in the first dropdown (higher order), so I might need to save extra state...but can anyone confirm?
From the documentation.....

When to save extra state

For blocks, if you have something lower in the order that depends on something higher in the order, you should duplicate that data and add it to your extra state.

For example, if you have a field that only exists if a next block is connected, you should add info about that next block to your extra state, so the field can be added to your block before the field’s state is deserialized.


Kiran Batchu

unread,
May 21, 2024, 11:02:30 AM5/21/24
to Blockly
I made some progress (I think...) re-reading the serializer documentation and I fixed the above issue using a Custom serializer
function createCustomSerializer() {
    const customSerializer = {
      load: function (state) {
        // Workspace-level serialization
        const workspace = Blockly.getMainWorkspace();
        const blocks = workspace.getAllBlocks();
        const block = blocks.find(function (b) {
          return b.type === mBlockName && b.data.CustomID === mBlockID;
        });

        // 1. Deserialize bottom dropdown value
        const datasetValue = state.DatasetName;

        // 2. Update top dropdown options based on bottom value
        const columnsList = getColumnsListOptions(datasetValue);
        // ? this.sourceBlock_ is null
        // this.sourceBlock_.updateDropdown("COLUMNS_FIELD", columnsList);
        block.updateDropdown("COLUMNS_FIELD", columnsList);

        // 3. Deserialize top dropdown value
        const columnValue = state.ColumnName;
        // this.sourceBlock_.setFieldValue(columnValue, "COLUMNS_FIELD");
        block.setFieldValue(columnValue, "COLUMNS_FIELD");
      },
      save: function () {
        // Workspace-level serialization
        const workspace = Blockly.getMainWorkspace();
        const blocks = workspace.getAllBlocks();
        const block = blocks.find(function (b) {
          return b.type === mBlockName && b.data.CustomID === mBlockID;
        });
        return {
          // DatasetName: this.sourceBlock_.getFieldValue("DATASET"),
          //  ColumnName: this.sourceBlock_.getFieldValue("COLUMNS_FIELD")
          DatasetName: block.getFieldValue("DATASET"),
          ColumnName: block.getFieldValue("COLUMNS_FIELD")
        };
      },
      priority: 100 // Set a high priority to run after built-in serializers
    };

    Blockly.serialization.registry.register(mSerializerName, customSerializer);
  }
However, When I serialize and then deserialize using --> Blockly.serialization.workspaces.load(serializedData, mBlocklyWorkspace);, I am getting a new error
Uncaught TypeError TypeError: g.clear is not a function
    at load$$module$build$src$core$serialization$workspaces (c:\PROJECTS\DataVizSandbox\WebGIS\node_modules\blockly\core\serialization\workspaces.ts:74:6)

From what I have read, the newer versions use 'workspace.clear()' or 'workspace.removeAll().....so why would I get this error message?
Thanks,

Mark Friedman

unread,
May 21, 2024, 12:50:10 PM5/21/24
to blo...@googlegroups.com
Kirin,

  I might suggest just using the field-dependent-dropdown plugin (doc here, demo here).  It has already solved the issues that you've been running up against.

  Hope this helps.

-Mark


--
You received this message because you are subscribed to the Google Groups "Blockly" group.
To unsubscribe from this group and stop receiving emails from it, send an email to blockly+u...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/blockly/4c0e1530-57c9-446c-8b3d-a031b8b44988n%40googlegroups.com.

Kiran Batchu

unread,
May 21, 2024, 1:55:14 PM5/21/24
to Blockly
Hi Mark,
Updating the child dropdown based on Parent dropdown is not the issue. That is working fine. The problem is when the Parent dropdown is below the Child dropdown and you serialize and then deserialize the block at the workspace level, the default deserializer does not set the value of the Child dropdown (since the deserializer has not set the value of the Parent dropdown as it is lower in the order) and throws the error which I have mentioned above.  The plugin that you mentioned states " Note that the parent field must be attached to the block before the child field".   So unfortunately this won't solve my problem. Hope, I am making sense.

Thanks,

Mark Friedman

unread,
May 21, 2024, 7:14:46 PM5/21/24
to blo...@googlegroups.com
Kiran,

  If I understand correctly, your only problem with using the field-dependent-dropdown is that you want your dependent dropdown to appear before its parent.  If that is the case, then I think one workaround would be to initially attach just the parent dropdown field and then insert, rather than append, the input containing the field-dependent-dropdown before the input containing the parent.  I'm suggesting this because I believe that the field-dependent-dropdown has already figured out the serialization issues that you are running into.

Here's an example of that, based on one of the examples given in the documentation for the field-dependent-dropdown:

Blockly.Blocks['dependent_dropdown_reverse_example'] = {
  init: function () {
    const parentFieldName = 'ANIMAL_CATEGORY';
    const childFieldName = 'ANIMAL';
    const parentOptions = [
      ['Mammal', 'mammal'],
      ['Bird', 'bird'],
      ['Cryptid', 'cryptid'],
    ];
    const optionMapping = {
      mammal: [
        ['Dog', 'dog'],
        ['Cat', 'cat'],
        ['Hamster', 'hamster'],
      ],
      bird: [
        ['Parakeet', 'parakeet'],
        ['Canary', 'canary'],
      ],
    };
    const defaultOptions = [['None available', 'noneAvailable']];
    const input = this.appendDummyInput();
    input.appendField('Animal')
         .appendField('Category')
         .appendField(new Blockly.FieldDropdown(parentOptions), parentFieldName);
    input.insertFieldAt(1,
        new FieldDependentDropdown(
            parentFieldName,
            optionMapping,
            defaultOptions,
        ),
        childFieldName);
  },
};


  If I've misunderstood your situation, I apologize.

-Mark


Reply all
Reply to author
Forward
0 new messages