Detect if a specific block in the blockly workspace is clicked?

32 views
Skip to first unread message

Ayham Elkhalifa

unread,
Sep 1, 2026, 10:03:51 AM (2 days ago) Sep 1
to Blockly
Working on a Blockly editor application, and I want to add a scratch-like rectangle, containing the value of the specific block when clicked.  My main problem is that I need code to detect if a block is clicked and released, but not dragged within the workspace or out of the toolbox. My editor uses Blockly v13.2.1 as of writing this.

Zoë Spriggs

unread,
Sep 1, 2026, 12:24:30 PM (2 days ago) Sep 1
to blo...@googlegroups.com
Hi Ayham, 
You should be able to do this by making a change listener for click events. Blockly click events are different from block drag events, so they only fire when something is clicked and released as you described. Click events fire regardless of what the user clicks (the workspace, the trashcan, blocks, etc.), but you can specify that the event's targetType is "block"

You can detect these events by doing something like this:

workspace.addChangeListener((event) => {
     if(event.type === Blockly.Events.CLICK && event.targetType === "block") { 
          //whatever action you'd like to do here
     }
});

Hope that helps, let me know if you have any other questions!

Best, 
Zoë

On Tue, 1 Sept 2026 at 08:04, Ayham Elkhalifa <ayham.e...@gmail.com> wrote:
Working on a Blockly editor application, and I want to add a scratch-like rectangle, containing the value of the specific block when clicked.  My main problem is that I need code to detect if a block is clicked and released, but not dragged within the workspace or out of the toolbox. My editor uses Blockly v13.2.1 as of writing this.

--
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/037a809d-9e9b-46b2-bf26-e8c9f50d62ccn%40googlegroups.com.

Ayham Elkhalifa

unread,
Sep 1, 2026, 11:03:09 PM (2 days ago) Sep 1
to Blockly
'addChangeListener' doesn't seem to be defined in workspace...

using it causes this error: Uncaught TypeError: Cannot read properties of undefined (reading 'addChangeListener')

Zoë Spriggs

unread,
Sep 2, 2026, 11:25:14 AM (yesterday) Sep 2
to blo...@googlegroups.com
Hmm, that's interesting. My guess is that your workspace is not yet injected by the time you call addChangeListener. If the workspace variable exists but is undefined, you could get that error. 

Do you have a line of code where you inject Blockly, something like: const workspace = Blockly.inject(...); ? If so, the workspace is assigned in that line of code. So you'll want to call workspace.addChangeListener(...) sometime after that Blockly.inject line

Also note that here, the name "workspace" could be anything, it's just a stand-in for whatever you're naming your own workspace (workspace, ws, myWorkspace, etc).

Let me know if that helps! If it doesn't, feel free to send over your code so I can help troubleshoot more directly. 

Best, 
Zoë



Ayham Elkhalifa

unread,
Sep 2, 2026, 12:06:59 PM (yesterday) Sep 2
to Blockly
Here's the code for my project. (Note, I'm using a custom generator, but the popups would execute javascript code)
core/init.js (runs after blockly/blockly.min.js and blockly/JavaScript_compressed.js)
/* there is lots of code above this comment, this is the only part that matters. workspaceData was defined right before this */
var workspace
function switchToolbox(tlbx) {
    fetch(tlbx).then(response => response.text()).then(xmlText => {
        const parser = new DOMParser()
        const toolbox = parser.parseFromString(xmlText, "text/xml").documentElement
        workspace = Blockly.inject("blocklyDiv", {toolbox: toolbox, ...workspaceData})
        workspace.addChangeListener(updateCode = () => {
            const code = Blockly.gdScript.workspaceToCode(workspace)
            document.getElementById("code").textContent = code
        })
        updateCode()
    })
}

switchToolbox("core/toolboxfd.xml")
console.log(Blockly.gdScript)
console.log(Blockly.JavaScript)
core/bubble.js (runs after core/init.js)
const BubbleData = {
    logic_null: () => `<div><b>number</b><br><span>null<span></div>`,
}

const mousePopup = document.getElementById("mousePopup")

let mousePopupTimeout = null
let mousePopupMove = null
let mousePopupStop = null

function showMousePopup(message, moveAlong) {
    if (mousePopupTimeout !== null) {
        clearTimeout(mousePopupTimeout)
        mousePopupTimeout = null
    }
   
    if (mousePopupMove !== null) document.removeEventListener("mousemove", mousePopupMove)
    if (mousePopupStop !== null) document.removeEventListener("mouseup", mousePopupStop)

    mousePopup.innerHTML = message
    mousePopup.style.display = "block"
    mousePopupMove = function(e) {
        mousePopup.style.left = `${e.clientX + 12}px`
        mousePopup.style.top = `${e.clientY + 12}px`
    }

    if (moveAlong) document.addEventListener("mousemove", mousePopupMove)
    mousePopupTimeout = setTimeout(hideMousePopup, workspaceData.popup.popupRemoveTimer)
}

function hideMousePopup() {
    mousePopup.style.display = "none"

    if (mousePopupTimeout !== null) {
        clearTimeout(mousePopupTimeout)
        mousePopupTimeout = null
    }

    if (mousePopupMove !== null) {
        document.removeEventListener("mousemove", mousePopupMove)
        mousePopupMove = null
    }

    if (mousePopupStop !== null) {
        document.removeEventListener("mouseup", mousePopupStop)
        mousePopupStop = null
    }
}

workspace.addChangeListener((event) => { // This is what causes the error (workspace.addChangeListener)
    if(event.type === Blockly.Events.CLICK && event.targetType === "block")
        showMousePopup(/* placeholder */ BubbleData.logic_null(), false)
})
showMousePopup(`<b>Header</b><br><span>Body</span>`, true) // Example popup
Message has been deleted

Zoë Spriggs

unread,
Sep 2, 2026, 1:44:31 PM (yesterday) Sep 2
to blo...@googlegroups.com
Thanks for providing your code, I think I see what's causing the error now.

In your function switchToolbox(), the Blockly.inject line only executes after the fetch() is complete. While Blockly.inject is queued up to run once the fetch() completes, the rest of your code executes. The click changeListener code likely executes before that fetch() finishes (and therefore before your workspace is actually injected, leaving workspace undefined). Since your workspace is undefined at the moment when the changeListener is added, you get the error.  

The other changeListener in your code works because it is also queued after that same fetch(), so it only runs after the Blockly.inject, meaning it gets the properly defined workspace. 

There's another issue with your setup regardless, which is injecting Blockly multiple times. When you re-inject Blockly to update the toolbox in subsequent calls to switchToolbox(), you're adding an additional workspace (which I assume you don't want since you're just trying to switch the toolbox). Even if you resolve the injection timing issue, the click changeListener would get added to the first workspace, but not subsequent ones. 

To solve both issues, I recommend injecting Blockly synchronously. This means that outside of your switchToolbox function, you should inject Blockly just once, and add both of your changeListeners. Rather than re-injecting Blockly whenever you want to update the toolbox, update your switchToolbox function to use workspace.updateToolbox(toolbox) instead.

The catch is that you must inject Blockly with a toolbox if you want to use updateToolbox later. But you can use a placeholder toolbox for injection and then update it. Just make sure that your placeholder is the correct toolbox type (either a flyout or category toolbox, depending on which you're using) otherwise Blockly will throw an error. For instance:
var tempToolbox = {
    "kind": "categoryToolbox",
    "contents": []
  };
workspace = Blockly.inject("blocklyDiv", {toolbox: tempToolbox, ...workspaceData});
switchToolbox("core/toolboxfd.xml");

Hopefully that all works for your use case! Again, let me know if that doesn't help or if you need more clarification, and we can keep troubleshooting :)

Best, 
Zoë


On Wed, 2 Sept 2026 at 10:09, Ayham Elkhalifa <ayham.e...@gmail.com> wrote:
What's weird is that workspace.addEventListener is defined at one point, but not at the other...

Ayham Elkhalifa

unread,
9:04 AM (8 hours ago) 9:04 AM
to Blockly
This works, but now there's the problem of the bubble always returning 'null'... (every block individually sets BubbleData)
Blockly.Blocks.strings_string = {
    init: function() {
        this.appendDummyInput().appendField("string")
            .appendField(new Blockly.FieldTextInput("abc"), "string")
        this.setOutput(true, "String")
        this.setColour("#f4df40")
    }
}
Blockly.gdScript.forBlock.strings_string = function(block) {
    let string = block.getFieldValue("string")
    BubbleData = `<span>"${fixXML(string)}"</span>`
    return [`"${string.replaceAll('"', '\\"').replaceAll('%', '%%')}"`, 2]
}

Zoë Spriggs

unread,
12:05 PM (5 hours ago) 12:05 PM
to blo...@googlegroups.com
Hi, 
I noticed that in your code, your click listener uses BubbleData.logic_null() as a placeholder. If you forgot to change that, the logic_null line could be the reason your bubble is always returning null. 

I'd like to make a couple quick notes that might help with future bugs:
- First, your BubbleData is initialized as an object in bubble.js, but overwritten as a string in the generator. This might be your intention, but I wanted to note it in case it wasn't. 
- Second, if your BubbleData is always going to be derived from a field value (like it is for the strings_string block right now) then you don't need to set BubbleData in the generator. Generators get called on every block, in an order based on their position in the workspace. If you had multiple blocks that were setting BubbleData in their generators, then the value of BubbleData would be whatever the last block generated (rather than the block that was clicked). Instead, you could compute the bubble content in the click listener. You can still access the appropriate Block object from within the click change listener, after you check to make sure that it's a block click event: const block = workspace.getBlockById(event.blockId);

If you want to show the generated code for each block instead, I can help with that as well. In that scenario, I'd still recommend keeping bubble logic separate so that the code generator only handles code generation. 

Best, 
Zoë


Reply all
Reply to author
Forward
0 new messages