Blockly: Update other inputDummy dropdown fields based on selection of a inputDummy dropdown field

1.7k Views Asked by At

I've been trying to make a custom block in the Blockly workspace that changes the options in drop-down fields based on the selection of a previous drop-down field in the same block. About the block:-

  1. There are three dropdown fields
  2. All are populated dynamically using Blockly.Extensions
  3. All codes are present below

I've implemented an 'onchange' function to the blockly initialization that gets the change data through the 'event' variable and responds accordingly. I'm having problems trying to update the changing drop-down fields. Please assist.

Code

// the block JSON declaration variable
var updateTableData = {
    "type": "al_update_table_data",
    "message0": "Update in table %1 where column %2 is %3 set value for column %4 to %5",
    "args0": [
        {
            "type": "input_dummy",
            "name": "table_input",
        },
        {
            "type": "input_dummy",
            "name": "column_input",
        },
        {
            "type": "input_value",
            "name": "get_value"
        },
        {
            "type": "input_dummy",
            "name": "column_input1",
        },
        {
            "type": "input_value",
            "name": "set_value"
        }
    ],
    "inputsInline": false,
    "previousStatement": null,
    "nextStatement": null,
    "fieldRow": false,
    "colour": 90,
    "tooltip": "Update value in a table",
    "helpUrl": "",
    "extensions": ["get_tables", "get_column", "get_column1"],
}
// the blockly extensions
// get list of tables and populate the 'table_input' drop-down field
Blockly.Extensions.register('get_tables', function () {
    this.getInput("table_input")
        .appendField(new Blockly.FieldDropdown(
            function () {
                let options = []
                let tables = JSON.parse(localStorage.getItem('applab_myTables'))
                tables.map(t => options.push([t.name, t.id]))
                return options
            }
        ), "table_input")
})

// get list of columns from the first table and populate the 'column_input' drop-down field
Blockly.Extensions.register('get_column', function () {
    this.getInput('column_input')
        .appendField(new Blockly.FieldDropdown(
            function () {
                let options = []
                let table = JSON.parse(localStorage.getItem('applab_myTables'))[0]
                Object.keys(table['columnData']).filter(cId => table['columnOrder'].includes(cId)).map(cId => options.push([table['columnData'][cId]['name'], cId]))
                return options
            }
        ), 'column_input')
})

// get list of columns from the first table, remove the column value already selected in 'column_input' and populate the 'column_input1' drop-down field
Blockly.Extensions.register('get_column1', function () {
    var selectedColumn = this.getFieldValue('column_input')
    this.getInput('column_input1')
        .appendField(new Blockly.FieldDropdown(
            function () {
                let options = []
                let table = JSON.parse(localStorage.getItem('applab_myTables'))[0]
                Object.keys(table['columnData']).filter(cId => table['columnOrder'].includes(cId)).filter(cId => cId !== selectedColumn).map(cId => options.push([table['columnData'][cId]['name'], cId]))
                return options
            }
        ), 'column_input1')
})
// Comments with 7 slashes (///////) are my commentary on the issues, errors and outputs that I get
// blockly block initialization
Blockly.Blocks['al_update_table_data'] = {
    init: function () {
        this.jsonInit(updateTableData)
    },
    onchange: function (event) {
        // console.log(event.type)
        var table_id = this.getFieldValue('table_input')
        var selectedcolumn = this.getFieldValue('column_input')
        var otherColumn = this.getFieldValue('column_input1')
        if (event.blockId === this.id) {
            if (event.type === Blockly.Events.BLOCK_CHANGE) {
                // console.log('name of changed field', event.name)
                // console.log('old value', event.oldValue)
                // console.log('new value', event.newValue)
                if (event.name === 'table_input') { 
                    // change in selected table, update column_input and column_input1
                } else if (event.name === 'column_input') {
                    // change in selected column, update column_input1
                    /////// I tried to removeField which did remove the field, but also the label on the same field row. But when I tried to getInput, I get the error: 'column_input1' input doesn't exist
                    // this.removeField('column_input1', true)
                    /////// I tried to removeInput as well, which too removed the field, but also the label on the same field row. And when I tried to getInput, I again get the error: 'column_input1' input doesn't exist
                    // this.removeInput('column_input1')
                    /////// This functions runs fine when I don't remove any input or field. But it keeps adding new drop-downs next to existing ones with new options
                    this.getInput('column_input1')
                        /////// I tried this to use removeField after getInput as well. But it shows the error: this.getInput().removeField() is not a function
                        // .removeField('column_input1')
                        .appendField(new Blockly.FieldDropdown(
                            function () {
                                let options = []
                                let table = JSON.parse(localStorage.getItem('applab_myTables')).find(table => table.id === table_id)
                                Object.keys(table['columnData']).filter(cId => table['columnOrder'].includes(cId)).filter(cId => cId !== event.newValue).map(cId => options.push([table['columnData'][cId]['name'], cId]))
                                return options
                            }
                        ), 'column_input1')
                }
            }
        }
        if (event.type === Blockly.Events.FINISHED_LOADING) {
            // workspace finished loading, update column_input and column_input1 based on selected table
        }
    }
}

Here is a snapshot of the block that is generated: enter image description here

tldr; Update options of a drop-down field in a block when the selected option of another drop-down is changed.

Thanks, Utkarsh

1

There are 1 best solutions below

0
On BEST ANSWER

Thanks to @beka from Google Blockly groups at https://groups.google.com/g/blockly/c/Vh_zPLrqjdw/m/TWHehaQ1AgAJ.

The main problem here was conceptual. A block has

  1. inputs: field rows that can have anything like value blocks (puzzle piece input) or statement blocks (lego input)
  2. fields: like from HTML. these can be text boxes, drop-downs, images, etc.

Both the input and the field can have names. And it is good practice to name them differently.

As seen in my extensions, I'd taken my input this.getInput('columnInput') and appended a field .appendField with the same name. Due to this, the input had a field with the same name.

Here are the corrections:

  1. to the extensions:

    Blockly.Extensions.register('get_column', function () {
    this.getInput('column_input')
        .appendField(new Blockly.FieldDropdown(
            function () {
                let options = []
                let table = JSON.parse(localStorage.getItem('applab_myTables'))[0]
                Object.keys(table['columnData']).filter(cId => table['columnOrder'].includes(cId)).map(cId => options.push([table['columnData'][cId]['name'], cId]))
                return options
            }
        ), 'column_input_field') // updated the field name different from input name
    })

  1. to the functions updating all the dropdowns inside the block initialization onchange

    this.getInput('column_input').removeField('column_input_field') // first removed the field
    // and then, append a new field with the new options
    this.getInput('column_input')
        .appendField(new Blockly.FieldDropdown(
            function () {
                let options = []
                let table = JSON.parse(localStorage.getItem('applab_myTables')).find(table => table.id === event.newValue)
                Object.keys(table['columnData']).filter(cId => table['columnOrder'].includes(cId)).map(cId => options.push([table['columnData'][cId]['name'], cId]))
                return options
            }
        ), 'column_input_field') // updated the field name different from input name

And ofcourse, if you personally face any issue while this.getInput('').removeField(''), log the input object console.log(this.getInput('')) and assess.