I have a side menu that is toggled by a hanmurger menu button. So here's what I have:
x-data="{ sideMenu: false }" on a wrapper div@click="sideMenu = ! sideMenu" on a button.:class="{ '-translate-x-64': ! sideMenu, 'translate-x-6': sideMenu }" on the side menuPretty basic stuff. I've used :class instead of x-show and 6 lines of code for x-transition with repetitive class names, and everything works up to this point. BUT if I add @click.away="sideMenu = false" to the side menu, the hamburger button stops working. But everything works if I use x-show and x-transition.
Any ideas?
@mokhosh - please post a pastable Alpine component to replicate the issue. Much easier for others than reconstructing one based on words. Thanks.
@calebporzio fair enough. here https://jsfiddle.net/7m46cvar/
You can delete line 11 to check the effect I talked about.
And here's the example that works https://jsfiddle.net/aqnhctb3/1/
@mokhosh i can't try because I'm from my mobile phone but I believe you just need to add the stop modifier to the first button (the one which shows your modal, see https://jsfiddle.net/k31bpvxo/).
Right now, you are clicking the button which sets the variable to true, then the event bubbles through the DOM up to window where the away listener catches it and sets the variable back to false.
I hope it makes sense.
Thanks, but why does it work in the second example without the .stop modifier?
@mokhosh It's due to the transition and the fact that javascript in browsers is a single thread (so only one instruction can run at a specific instant).
When you apply a transition, the items takes a some time to become visible and, if you have a look at the source code > on.js > line 10, the away modifier doesn't do anything when the element is hidden. We are talking about milliseconds for normal transition but it's enough for the event to bubble to the top.
To recap:
In the first example
In the second example
I bet we could wrap the show() function (utils.js line 141) in a requestAnimationFrame() call to make it consistent so it won't confuse people and it won't require the stop modifier as you were expecting.
It sounds like a good call to me, let's see what Caleb thinks.
I was having a look at this old ticket again and I think it's going to be a wontfix.
The original example didn't use transitions so even changing utils.js won't bring any benefits.
I've looked again into the example and I don't think there is a bug there.
There are 2 concurrent event listeners doing opposite things on a click event (Clicking on the button means clicking away from the other div). Javascript is single threaded so only 1 instruction runs at a specific moment which means that one of the 2 has to happen first.
Because of the way events work (bubbling up the DOM), window events always run after the local ones.
Concurrency issues like this one depend on the application design so there's no magic rule to fix them all. Changing that behaviour only to make this example work would be a breaking change for others and also, it would be counterintuitive to anyone reading the documentation and knowing how events work.
In situation like this where multiple listener compete for the same updates, developer need to put some logic to avoid conflict. The easiest way is to prevent the event triggering the initial animation from bubbling up the dom.
Would you mind if we close this issue @mokhosh? Thanks
I know what you're saying @SimoTod, but there is still some strange behavior. For example, this is on the alpinejs docs:
<div x-data="{ open: false }">
<button @click="open = true">Open Dropdown</button>
<ul
x-show="open"
@click.away="open = false"
>
Dropdown Body
</ul>
</div>
This is basically the same as my first example that doesn't work. There's no .stop modifier but it works as expected.
Yeah that work because alpime skips the event if your element is already hidden.
See https://github.com/alpinejs/alpine/blob/master/src/directives/on.js#L10
Your example is different: your lateral menu was technically visible, just translated off screen.
We can update the documentation to make the 'skipped when invisible thing' obvious but the rest seems okay: it says that the away modifiers runs when you click away from the element.
What do you suggest the right behavior should be?
Do you have a solution in mind?
That makes sense, it just needs some clarification and examples i guess.
Thanks for taking the time.
Most helpful comment
@mokhosh
i can't try because I'm from my mobile phone butI believe you just need to add the stop modifier to the first button (the one which shows your modal, see https://jsfiddle.net/k31bpvxo/).Right now, you are clicking the button which sets the variable to true, then the event bubbles through the DOM up to window where the away listener catches it and sets the variable back to false.
I hope it makes sense.