Dynamic dropdown list for select a generated procedure.

107 views
Skip to first unread message

Drake Taylor

unread,
Mar 18, 2025, 4:58:42 PM3/18/25
to Blockly
Hello,
I'm trying to make a dynamic dropdown that features all the procedures within the workspace.

So far I have the list generating the options and whenever the selected procedure is deleted it removes it resets the dropdown to it's default option.

My current problem is that I want to use the renameProcedure hook to update the selected option, well, when they rename the procedure. The problem I'm running into is that the options don't regenerate before the renameProcedure hook is called (They actually don't regenerate at all, before or after, not until the user manually clicks the dropdown list).

What I'm wondering is if there is a way to manually regenerate the list, so that I can update the field. I tried field.getOptions(false), in hopes that it would regenerate the internal options as well, but it seems that it doesn't (which seems like it should).

Bellow will be my current code for any recommendations:

// Option Generator
function (this: Blockly.Field) {
  const sourceBlock = this.getSourceBlock()
  const list = [['nothing', 'null']] as [string, string][]
  if (!sourceBlock || !sourceBlock.workspace) return list
  const tuple = Blockly.Procedures.allProcedures(sourceBlock.workspace)
  for (let i = 0; i < tuple.length; i++) for (let j = 0; j < tuple[i].length; j++) list.push([tuple[i][j][0], tuple[i][j][0]])
  return list
}

// Rename Procedure Hook
function (this: Blockly.Block, oldName: string, newName: string) {
  const field = this.getField(fieldName) as Blockly.FieldDropdown
  const value = field.getValue()
  if (!field || !value || !Blockly.Names.equals(oldName, value)) return
  field.setValue(newName)
}

// On Change Listener
function (this: Blockly.Block, event: Blockly.Events.BlockChange) {
  if (!this.workspace || this.workspace.isFlyout || event.type !== Blockly.Events.BLOCK_DELETE) return
  const value = this.getFieldValue(fieldName)
  if (value === 'null' || Blockly.Procedures.getDefinition(value, this.workspace)) return
  this.setFieldValue('null', fieldName)
}

I'm using a helper function which wraps these functions, which is where fieldName is defined.

epas...@google.com

unread,
Mar 19, 2025, 1:02:19 PM3/19/25
to Blockly
Hi Taylor,

Just to make sure I understand:
  • You're primarily looking at the Rename Procedure Hook function.
  • You've verified it gets called for each drop-down field when the procedure is renamed.
If that's the case, I think you were on the right track with calling field.getOptions(false). That would update the internal list of options and you should be able to call field.setValue(newName) after that. If that's not working, I'd suggest adding a breakpoint to the start of that function and stepping through the code to see where things aren't working as expected. You should also be able to inspect the list of options after each step to see what's happening.

Cheers,
Erik

Drake Taylor

unread,
Mar 19, 2025, 2:43:41 PM3/19/25
to Blockly
Hi Erik,

I appreciate the response, but unless I'm missing something field.getOptions(false) doesn't seem to be updating the internal list, it does run the generator again, but when calling field.setValue(newName) afterwards it gives the warning that it cannot set the dropdown's value to an unavailable option.

// renameProcedure
function (this: Blockly.Block, oldName: string, newName: string) {
  const field = this.getField(fieldName) as Blockly.FieldDropdown
  const value = field.getValue()
  if (!field || !value || !Blockly.Names.equals(oldName, value)) return
  field.getOptions(false) // Calls the options generator
  field.setValue(newName) // Throws warning that it cannot set dropdown's value to unavailable option
}

I've also tried delaying the setValue using setTimeout, in-case there was some form of asynchronous shenanigans, but that also doesn't fix the problem.

Finally, just in-case I confirmed that the version I'm using is the latest Blockly version (11.2.1).

Thanks,
Drake

epas...@google.com

unread,
Mar 19, 2025, 4:26:00 PM3/19/25
to Blockly
Hi Taylor,

The only place the menuGenerator is called the result is set to the internal list of options, so if your options generator is being called it's also being set internally. Have you confirmed through debugging or logging that the list of options being generated is correct and that the newName matches the item in the list of options?

Drake Taylor

unread,
Mar 19, 2025, 5:41:16 PM3/19/25
to Blockly
Hello,

I found my solution, the problem I found is that Blockly.Procedures.allProcedures method still returns the old name, and simply calling field.getOptions(false) needed to be deferred to the next javascript tick. simply wrapping the last two lines of renameProcedure in a setTimeout(() => { ... }, 0) solves the issue.

I'm not sure if that should be expected behavior, or if there is another way to get allProcedures that returns the updated names that tick.

Thanks for the help,
Drake

epas...@google.com

unread,
Mar 19, 2025, 6:22:25 PM3/19/25
to Blockly
Ah, that makes sense. I'd guess the callback happens before it finalizes the new name in the list of procedures so that handlers can act on it before it changes. Your solution sounds like a good approach to me!

Roihan Abidin

unread,
Mar 24, 2025, 11:31:13 AM3/24/25
to blo...@googlegroups.com

yo


--
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 visit https://groups.google.com/d/msgid/blockly/bff9ef98-200a-4ee0-bb11-100c33e69c90n%40googlegroups.com.
Reply all
Reply to author
Forward
0 new messages