Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Trigger components methods when click elsewhere

I've a HeaderSubmenu component which can show/hide a drop-down menu if a button is clicked. Okay, but now I try to find a good solution to make that if the user clicks anywhere in the app but not on this drop-down menu, it hides.

I'm using Vue 2.3.3 with Vuex and VueRouter.

my App entry point :

'use strict';

import Vue from 'vue';
import VueRouter from 'vue-router';
import Vuex from 'vuex';

Vue.use(VueRouter);
Vue.use(Vuex);

import store_data from 'store';
import {router} from 'routes';

import App from 'app.vue';

var store = new Vuex.Store(store_data);

new Vue({
  el: '#app',
  store,
  router: router,
  render: function (createElement) {
    return createElement(App)
  }
})

template of HeaderSubmenu component :

<template>
  <li class="header-submenu">
    <!-- button to show/hide the drop-down menu -->
    <header-link v-bind="$props" :href="to ? false : '#'" @click.native="toggleMenu()">
      <slot name="item"></slot>
    </header-link>
    <!-- drop-down menu -->
    <ul class="submenu-list" :class="{'open': open, 'animated': animated}" @animationend="displaynone()">
      <slot></slot>
    </ul>
  </li>
</template>

This component is somewhere in my app, and I want to call his toggleMenu() method when the users clicks elsewhere than on the <ul class="submenu-list">.

I thought of a global event bus where my drop-down menu should be "registred" and detects on a global @click event on my App. If the registered menu is not the element clicked we'd call his toggleMenu() method if not. (Ideally I could register other elements which have the same behaviour.)

... But I don't master vue event system at all. How can check if an event is not on some element ? I've no idea of how to achieve this. Can you help me ? Thank you !

====== EDIT ======

With the help of Bert Evans I used this custom directive :

// directive-clickoutside.js
export default {
  bind(el, binding, vnode) {
    el.event = function (event) {
      // here I check that click was outside the el and his childrens
      if (!(el == event.target || el.contains(event.target))) {
        // and if it did, call method provided in attribute value
        vnode.context[binding.expression](event);
      }
    };
    document.body.addEventListener('click', el.event)
  },
  unbind(el) {
    document.body.removeEventListener('click', el.event)
  },
};

// main.js
import clickout from 'utils/directive-clickoutside';
Vue.directive('clickout', clickout);

I used this in my component template :

// HeaderSubmenu component
<template>
  <li class="header-submenu">
    <!-- élément du header servant à ouvrir le sous-menu -->
    <header-link v-bind="$props" :href="to ? false : '#'" @click.native="toggle()">
      <slot name="item"></slot>
    </header-link>
    <!-- sous-menu -->
    <ul class="submenu-list" :class="{'open': open, 'animated': animated}" @animationend="displaynone()" v-clickout="hide()">
      <slot></slot>
    </ul>
  </li>
</template>

But this code fails when I click anywhere on the page :

Uncaught TypeError: n.context[e.expression] is not a function
    at HTMLBodyElement.t.event (directive-clickoutside.js:7)

Where is the problem ?

like image 868
Thaledric Avatar asked Jan 04 '23 20:01

Thaledric


1 Answers

The issue is here.

v-clickout="hide()"

Effectively what you are doing is setting v-clickout to the result of hide(). Instead, just pass it the hide method.

v-clickout="hide"

In general with Vue, in your template, if you are just wanting the template to call a function and not do anything special, just pass it the name of the function.

like image 68
Bert Avatar answered Jan 06 '23 13:01

Bert