Stimulus has no focusout event trigger? not playing nice with bootstrap?

Thanks to all contributors! I am using stimulus and bootstrap. I now want to collapse a bootstrap accordion when the user clicks outside of the accordion.

Problem is that the common ‘focusout’ action (and blur) don’t seem to trigger. If i switch the trigger to ‘click’, that works. I am confused. Am I doing something wrong? Is bootstrap not playing nice? Does stimulus not have the focusout event trigger?

HAML

%button.collapsed.btn{id: "menu", type: "button", "data-bs-toggle": "collapse", "data-bs-target": "#menu_expandable", "aria-controls": "navbarToggleExternalContent", "aria-expanded": "false", "aria-label": "Toggle navigation" }
	= image_pack_tag "icons/menu_black_24dp.svg", class: "my_icon img_fluid"
#accordion1.accordion{data: {controller: "menu", action: 'focusout->menu#collapseMe'}}
	.accordion-item
		#menu_expandable.accordion-collapse.collapse.seriously-nopad.my-accordion{data: {"menu-target": "accordionCollapsable"},"data-bs-parent":"#accordion1", "aria-expanded":"false", "aria-labelledby":"#menu", "data-backdrop": "static"}
            %p= "The accordion is open"

JS

import ApplicationController from './application_controller'

export default class extends ApplicationController {
	static targets = ['accordionCollapsable']
	connect () {
		super.connect()
	}
	collapseMe(){
		console.log("focusOut event!!!")
	}
}

I’ve never actually added a focusout event listener myself, so I’m not strictly answering your question, but I use useClickOutside from the wonderful stimulus-use collection of composable controllers instead for similar behaviour. Stimulus-Use - A collection of composable behaviors for your Stimulus Controllers (It might seem like overkill when a vanilla event listener should be sufficient, so disregard this comment if you want to solve the actual issue.)

There’s the blur event:

There’s also the click@window-> action that you can catch:

Yeah, I tried the blur event, but that did not work either.

I looked at click@window, it seems to trigger every time the user clicks anything within the site. Is the idea to have a “click” on target and a click@window and then when click@window triggers but “click” does not, we know that the user clicked somewhere else? Thats pretty smart and should work, I do wonder however whether there are speed concerns to triggering an event every time the user clicks a button.

Wow never seen that library before, looks great! However, I’ll try vanilla event listeners first. Adding more imports as a first response goes against my grain.

It’s a valid concern. But so long as you very quickly issue a return if not applicable (e.g. presence of a class on an element proving it is hidden, which is a single-access operation), then the cost will be low.

I can absolutely see that and I almost always agree with that sentiment, but stimulus-use is great and I can’t recommend it enough. The library is tiny to begin with, but you can tree-shake as well so the bundle size increase will be minimal. I use useDebounce, useIntersection, useClickOutside and probably more I can’t remember. I love that they’re behaviours you add, so I can use my own structure without importing other people’s controllers wholesale.

I ended up solving this with the blur event. However, there are two tricks which were required for me to get blur to work. First, I needed to set the blur capture parameter to true. Then I needed to set the data attribute of the container “tab-index = -1”.

If a future reader is trying to do what I was doing (collapse a bootstrap accordion when the user clicks outside) beware it gets messy quick. Anyone else trying to solve this problem should check this out. stackoverflow.com/questions/152975/…

Scroll down to the comment by 7 revs where it says in big “You don’t actually want to bind click handlers”. He explains it perfectly. Beware you need timeouts, and timeout nonsense is messy!!!