Fixed XF JS Events don't bubble up the DOM tree

Lukas W.

Well-known member
Affected version
2.3 Beta 1
It looks like all events triggered with XF.customEvent are configured to not bubble up through the DOM. The constructor specifically deletes the bubbles option from the config. As such, it is impossible to attach any listener to such an event if the event target is not immediately present when the JS is loaded, such as when the target is inside a modal.

In my specific instance I have an auto-complete field inside a modal, that I have attached a listener to with the code below. Both the target, and the JS are loaded through the modal. Since the target is not yet present when the XF loader loads the JavaScript in, the event listener can't be immediately attached to the event, so I've chosen the document body as target instead.

JavaScript:
XF.on(document.body, 'change pase auto-complete:insert', function (event) {
    const target = event.target.closest('<actualTargetSelector>');
    if (!target) {
     return;
    }

    // Do stuff
});

When the auto-complete:insert event is fired however, it is triggered through XF.trigger(this.target, XF.customEvent('auto-complete:insert')), and as such runs without bubbles: true, even if it was defined on the specific trigger section (which it is not).

This appears to be a general problem with how XF triggers events throughout the code. jQuery events always bubble upwards unless specifically configured not to, so it's probably preferable to change XF.trigger to always bubble events up (unless configured otherwise).
 
It will also bind to the original element, in my case the document body and listen for events for the actual target but never receive them due to them not bubbling up. Im not sure XF.onDelegated works at all considering that aspect
 
We'll take a closer look at the defaults but, just to clear something up, XF.customEvent deletes the options after passing them through the parent constructor (as the extra options are set as local properties). XF.customEvent('some-event', { bubbles: true }) works as expected.

Also XF.onDelegated does serve the purpose in the original post and should work (it's used in the core in quite a few places):

JavaScript:
XF.onDelegated(document, 'change paste auto-complete:insert', '<actualTargetSelector>', (e) =>
{
    // Do stuff
})
 
We'll take a closer look at the defaults but, just to clear something up, XF.customEvent deletes the options after passing them through the parent constructor (as the extra options are set as local properties).
Huh, so it does, thanks for clarifying. While that does allow it to bubble up in theory, with the exception of one place, the core code doesn't seem to make use of it however.
Also XF.onDelegated does serve the purpose in the original post and should work (it's used in the core in quite a few places):
I must admit I don't see how that could be the case. After some wrap magic, the code still boils down to XF.on(document, ...), which would never receive the triggered event if it does not bubble. It does work for core JS events, which does appear to be your main (or even only) use case from what I see on a quick glance, but it doesn't solve the issue of custom events not being available at that dom node.

Also on that matter, is there a specific reason to have XF.onDelegated as a separate method? It seems to me it would have better served as an overload option to the default XF.on, similar to how XF.ajax handles an optional callback function or data not being provided.
 
Thank you for reporting this issue, it has now been resolved. We are aiming to include any changes that have been made in a future XF release (2.3.0 Beta 2).

Change log:
Mark custom events as bubbling and cancelable by default
There may be a delay before changes are rolled out to the XenForo Community.
 
Top Bottom