javascriptblocklygoogle-blockly

How to hide/remove field in Blockly?


How to hide a field based on the dropdown value change.

I added a input field called 'A'. I have a drop-down field. If I select a value in the drop down, say 'Remove field A', then the input field should be removed.

I tried removeField. But it did not work. Any other methods? or how to use remove-field correctly?

    this.appendDummyInput()
      .setAlign(Blockly.ALIGN_RIGHT)
      .appendField('Type ')
      .appendField(new Blockly.FieldDropdown(typeOptions), 'columnType');

    // if columnType = Card, show the following:
      this.appendDummyInput()
        .setAlign(Blockly.ALIGN_RIGHT)
        .appendField(' Card: ')
        .appendField(new Blockly.FieldDropdown(cardsList), 'cardValue');

    //  if columnType = view, show the following:
      this.appendDummyInput()
        .setAlign(Blockly.ALIGN_RIGHT)
        .appendField(' View ')
        .appendField(new Blockly.FieldDropdown(viewsList), 'viewValue');

Solution

  • Okay, so this doesn't have your full code, but I think I see your problem here.

    The short answer is that, in your provided code, I don't see you doing anything in the callback functions when selecting a new value in your block, nor do I see you saving it from/reading it from the XML. It's possible some of this was left out, but in the interest of not making you play "include more code" tag in the comments, I'll just do a rundown here.

    Let me show you some sample code and walk through what all I do in order to make this case work:

    Blockly.Blocks['mySampleBlock'] = {
        /**
         * Initiate the block. This runs before domToMutation.
         */
        init: function () {
            var typeOptions = [['Card', 'card'], ['View', 'view']];
            this.appendDummyInput()
                .setAlign(Blockly.ALIGN_RIGHT)
                .appendField('Type ')
                .appendField(new Blockly.FieldDropdown(typeOptions, this.handleTypeSelection.bind(this)), 'typeSelector');
            // Initialize the value of this.columnType (used in updateShape)
            this.columnType = this.getFieldValue('typeSelector');
            // Avoid duplicating code by running updateShape to append your appropriate input
            this.updateShape();
            //@TODO: Do other block configuration stuff like colors, additional inputs, etc. here
        },
        /**
         * This function runs each time you select a new value in your type selection dropdown field.
         * @param {string} newType This is the new value that the field will be set to.
         * 
         * Important note: this function will run BEFORE the field's value is updated. This means that if you call
         * this.getFieldValue('typeSelector') within here, it will reflect the OLD value.
         * 
         */
        handleTypeSelection: function (newType) {
            // Avoid unnecessary updates if someone clicks the same field twice
            if(this.columnType !== newType) {
                // Update this.columnType to the new value
                this.columnType = newType;
                // Add or remove fields as appropriate
                this.updateShape();
            }
        },
        /**
         * This will remove old inputs and add new inputs as you need, based on the columnType value selected
         */
        updateShape: function () {
            // Remove the old input (so that you don't have inputs stack repeatedly)
            if (this.getInput('appendToMe')) {
                this.removeInput('appendToMe');
            }
            // Append the new input based on the value of this.columnType
            if(this.columnType === 'card') {
                // if columnType = Card, show the following:
                //@TODO: define values in cardsList here
                var cardsList = [['Dummy Option','option']];
                this.appendDummyInput('appendToMe')
                    .setAlign(Blockly.ALIGN_RIGHT)
                    .appendField(' Card: ')
                    .appendField(new Blockly.FieldDropdown(cardsList), 'cardValue');
            } else if (this.columnType === 'view') {
                //  if columnType = view, show the following:
                //@TODO: define values in viewsList here
                var viewsList = [['Dummy Option','option']];
                this.appendDummyInput()
                    .setAlign(Blockly.ALIGN_RIGHT)
                    .appendField(' View ')
                    .appendField(new Blockly.FieldDropdown(viewsList), 'viewValue');
            }
        },
        /**
         * This function runs when saving your block to XML. This is important if you need to save your block to XML at any point and then either
         * generate code from that XML or repopulate your workspace from that XML
         */
        mutationToDom: function () {
            var container = document.createElement('mutation');
            // Do not use camelCase values for attribute names.
            container.setAttribute('column_type', this.columnType);
            // ALWAYS return container; this will be the input for domToMutation.
            return container;
        },
        /**
         * This function runs when loading your block from XML, after running init.
         * It's very important for updating your block in response to values selected in a field.
         */
        domToMutation: function (xmlElement) {
            // This attribute should match the one you used in mutationToDom
            var columnType = xmlElement.getAttribute('column_type');
            // If, for whatever reason, you try to save an undefined value in column_type, it will actually be saved as the string 'undefined'
            // If this is not an acceptable value, filter it out
            if(columnType && columnType !== 'undefined') {
                this.columnType = columnType;
            }
            // Run updateShape to append block values as needed
            this.updateShape();
        }
    };
    

    A few things to note about this situation, in addition to my explanatory comments:

    1. You don't strictly have to use my this.columnType construction. Instead, you can pass a columnType value into updateShape and use this.getFieldValue('typeSelector') or the input of your 'callback' function (handleTypeSelection). I tend to prefer this because I often make much more complicated blocks where it's hard or inefficient to get the appropriate value every time, and this.whateverMyValueNameIs is easier.
    2. Likewise, instead of this.removeInput and this.appendDummyInput in updateShape, you can use removeField and appendField, as was your first instinct. However, if you do this, you will need to make sure that you have named the input you intend to append your field to/remove it from. I tend to prefer just adding/removing the entire input in most cases because it also lets me change out labels, etc.
    3. Any time you're making any changes in response to the value of a dropdown, you should probably be adding domToMutation and mutationToDom to save that value to the mutation attribute and then read it out and update your block accordingly. This applies even if you don't have an actual mutator on your block.
    4. Pay attention to the TODO comments in here; since I didn't know the values for viewsList and cardsList, I didn't provide them, nor did I provide any other block configuration for you.

    This can be a bit confusing, so please ask any follow-up questions if you have them. It took me a while to get the hang of it, myself. (I may request additional code samples from you if I'm not clear on what you're trying to do, though.)

    Good luck!