Home > OS >  how can I resolve a collision between two events, click outside and on the button to close a menu in
how can I resolve a collision between two events, click outside and on the button to close a menu in

Time:07-07

I have a problem in svelteKit with a collision. The button opens the menu but when I try to close it, it doesn't. When I click outside the button, this triggers handleClickOutside which calls clickOutside.js, and then closes the menu.

I think that the problem is that element.contains includes the button and for that, the menu didn't close.

however, I couldn't fix it.

I'm using tailwindUI, tailwindCSS, SvelteKit.

index.svelte

<script>
    // @ts-nocheck
    import bostonLogo from '../../img/bostonLogo.png';
    import { clickOutside } from '../../lib/clickOutside';

    // Example Profile
    let profile = {
        name: 'Matias',
        lastName: 'Barletta'
    };

    // Show/Hide Menu

    let menu = false;

    // COLLISION WITH HANDLENAV
    
    function handleClickOutside(event) {            
        menu = false;   
    }

    function handleNav() {

        menu = !menu;
        
    }

</script>

<div>
    <!-- Static sidebar for desktop -->
    <div  class:hidden={!menu}>
        <!-- Sidebar component, swap this element with another sidebar if you like -->
        <div
            use:clickOutside
            on:click_outside={menu? handleClickOutside : ''}
            class:absolute={menu}
            class:mt-11={menu}
            
        >
...

clickOutside.js

// @ts-nocheck
/** Dispatch event on click outside of element */
// @ts-ignore

export function clickOutside(element) {
    // @ts-ignore

    const handleClick = (event) => {
        console.log(event.target, document.body)
    
        // element exist?, element contain where i did click, preventDefault = false?
        if (element && !element.contains(event.target) && !event.defaultPrevented) {
            element.dispatchEvent(
                // Dispatch and create new custom event.
                new CustomEvent('click_outside', element)
            );
        }
    };
    // add eventlistener when you click on document
    document.addEventListener('click', handleClick, true);

    return {
        destroy() {
            document.removeEventListener('click', handleClick, true);
        }
    };
}

CodePudding user response:

I've had this exact same issue not too long ago. My solution was to just ignore the menu click when the menu is open. You already have this: on:click_outside={menu? handleClickOutside : ''}.

So now if the menu is closed, the action won't fire. If the menu is open, the menu button won't fire. So there's always just 1 callback that's being called.

function handleNav() {
    if (menu) return;
    menu = !menu;
}

As an Alternative you could add options to the action so that the handleClick function can ignore the click if the target is within a list of ignored selectors (rather than just the element it is on).

Update

The alternative approach here, with a working REPL.

What we do, is we added an ID to the menu button

<button
  id="menu-button"
  type="button"
  value="button"
  
  on:click={handleNav}
  >...</button>

We then add that ID as a parameter to the use:clickOutside action:

<div
  use:clickOutside={{ignore: 'menu-button'}}
  on:click_outside={menu? handleClickOutside : ''}
  class:absolute={menu}
  class:mt-11={menu}
  
  >
  <div >
    Some menu content here
  </div>
</div>

And then in the action, we get the element by ID and check if we're in it.

const handleClick = (event) => {
  event.preventDefault();
  
  // This function kinda rearranged with early returns to take the new parameter into account.
  if (!element) return;
  if (element.contains(event.target)) return;
  
  //Get the element based on the id we gave in the params
  const ignore = document.getElementById(opts.ignore)
  //Check that we're not clicking in the button.
  if (ignore.contains(event.target)) return;
  
  element.dispatchEvent(
    // Dispatch and create new custom event.
    new CustomEvent('click_outside', element)
  );
};
  • Related