Invoke actions when clicking outside of components

Currently, I have a HeaderSubmenu component that is designed to show/hide a drop-down menu when a specific button is clicked. However, I am now trying to implement a solution where if the user clicks anywhere else in the application other than on this drop-down menu, it should automatically hide.

For my setup, I am utilizing Vue 2.3.3 along with Vuex and VueRouter.

This is the entry point of my App:

'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)
  }
})

Here is the template for the HeaderSubmenu component:

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

The goal is to call the toggleMenu() method of this component whenever the user clicks outside the <ul class="submenu-list">.

I have been considering using a global event bus mechanism where the drop-down menu would be 'registered' to detect any click events within the entire application. If the registered menu is not the element clicked, then its toggleMenu() method would be triggered. Ideally, other elements with similar behavior could also be registered in this manner.

However, I currently lack a clear understanding of Vue's event system and how to determine if an event was triggered outside of a specific element. Can anyone provide guidance or assistance? Thank you!

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

After consulting with Bert Evans, I implemented a custom directive as follows:

// directive-clickoutside.js
export default {
  bind(el, binding, vnode) {
    el.event = function (event) {
      // Check if the click occurred outside the element and its children
      if (!(el == event.target || el.contains(event.target))) {
        // If true, call the method provided in the 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);

Incorporating this directive into my component template:

// HeaderSubmenu component
<template>
  <li class="header-submenu">
    <!-- Element in the header used to trigger the submenu -->
    <header-link v-bind="$props" :href="to ? false : '#'" @click.native="toggle()">
      <slot name="item"></slot>
    </header-link>
    <!-- Submenu content -->
    <ul class="submenu-list" :class="{'open': open, 'animated': animated}" @animationend="displaynone()" v-clickout="hide()">
      <slot></slot>
    </ul>
  </li>
</template>

However, upon testing, I encountered the following error when clicking anywhere on the page:

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

What could be causing this issue?

Answer №1

The problem lies right here.

v-clickout="hide()"

Essentially, what you're doing is assigning the result of hide() to v-clickout. Instead, simply provide it with the hide function.

v-clickout="hide"

Generally speaking in Vue, when working with templates, if you just want the template to execute a function without any specific handling, just pass the name of the function.

Answer №2

Follow these steps to implement the click-outside functionality in Vue 3:

app.directive('click-outside', {
  // hook function for binding
  beforeMount(el, binding) {
    el.clickOutsideEvent = function (event) {
      if (!(el == event.target || el.contains(event.target))) {
        binding.value(event)
      }
    }
    document.body.addEventListener('click', el.clickOutsideEvent)
  },

  // hook function for unbinding
  unmounted(el) {
    document.body.removeEventListener('click', el.clickOutsideEvent)
  },
})

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Troubleshooting the display of API-generated lists in Angular 8

I am encountering an issue in Angular 8 when trying to display my list on a page. Below is the code from my proposal-component.ts file: import { Component, OnInit, Input } from "@angular/core"; import { ActivatedRoute, Params } from "@angular/router"; imp ...

What is the method for launching a standalone terminal window from a vscode extension?

I am in the process of creating a custom extension for Visual Studio Code. My goal is to open a separate terminal window and execute multiple commands consecutively, similar to Terminal.sendText but not within the integrated terminal. Is there a method to ...

Struggling to delete event listeners in TypeScript using object-oriented programming with JavaScript

After researching the issue, I have discovered that the onMouseUp event is being fired but it is not removing the EventListeners. Many individuals facing a similar problem fail to remove the same function they added initially. Upon reading information fr ...

What is the best way to transfer an array from an Express Server to an AJAX response?

My AJAX request successfully communicates with the server and receives a response that looks like this: [{name: 'example1'}, {name: 'example2'}] The issue arises when the response is passed to the client-side JavaScript code - it is t ...

Combining the power of Angular.js and Require.js

As I develop a local app on nw.js using angular.js, I'm starting to question my approach. In my controller, I often find myself writing code like this: .controller('UserSettingsCtrl', function($scope, $mdDialog, $translate) { var fs = ...

Can you explain the meaning of the code `@input="$emit('input', $event)" used in a Vue component?

I have come across some code that I am looking to revise: <b-input :value="value" @input="$emit('input', $event)" ref="input" :maxlength="maxlength"/> Can someone explain what @input="$emit('input', $event)" means? Where should ...

Is it possible for AJAX and PHP to collaborate seamlessly?

Currently, I'm facing an issue where my Js file is failing to recognize a php variable that is generated by ajax. Let's take a look at the structure: index.php: <script src="js.js"> </script> <? include('build.php'); &l ...

Tips for converting NULL% to 0%

When running my calculatePercents() method, I am receiving NULL% instead of 0%. Upon checking my console.log, I noticed that the values being printed are shown as NULL. calculatePercents() { this.v.global.total.amount = this.v.global.cash.amount + ...

Utilizing the power of jQuery's `.clone` method to create a new window with fully

I am currently working on a project where I need to duplicate a page while maintaining its functionality, and then display it in a new window. My goal is to only clone a specific portion of the page, but for now I am focusing on replicating the entire page ...

Tips for modifying an HTML element's attribute when a button is clicked, both in the client and server side

Context: This question delves into the fundamental concepts of AJAX. I have been studying this tutorial along with this example for the JavaScript part, and this resource (the last one on the page) for the PHP segment. Imagine a scenario where a JavaScri ...

Tips for updating React context provider state when a button is clicked

WebContext.js import React, { createContext, Component } from 'react'; export const WebContext = createContext(); class WebContextProvider extends Component { state = { inputAmount: 1, }; render() { return <WebC ...

Juggling numerous pages in a React application

Our application is primarily flask/python based, with flask handling everything from user sessions to URLs. We are currently in the process of developing a UI component for our application using reactjs. We have successfully built the react bundle (bundle ...

Can someone show me how to implement arrow functions within React components?

I am facing an issue while working on a node and react project. Whenever I use an arrow function, it shows an error stating that the function is not defined. Despite trying various tutorials and guides, I am unable to resolve this issue. Below is the snipp ...

Even after setting the handler to return false, Angular continues to submit the form

In the following scenario, I have encountered an issue: Here is the HTML template snippet: <form [action]='endpoint' method="post" target="my_iframe" #confirmForm (ngSubmit)="submitConfirmation()"> <button type="submit" (click)="conf ...

Sending JSON data stored in a JavaScript variable through a jQuery POST request

I am currently attempting to retrieve data from a remote location using a JQuery post method. It works perfectly fine when I directly input the data to be posted, but for some reason, it fails when I store the JSON in a JavaScript variable and pass it in. ...

Retrieve vuex state in a distinct axios template js file

I have encountered an issue with my Vue project. I am using Vuex to manage the state and making axios requests. To handle the axios requests, I created a separate file with a predefined header setup like this: import axios from 'axios' import st ...

Guide to transferring parameters from one function to another in Javascript

For my automation project using Protractor and Cucumber, I have encountered a scenario where I need to use the output of Function A in Function B. While I was able to do this without Cucumber by extending the function with "then", I am facing difficulties ...

Locate the row just before the last one in the table

I have a table that dynamically creates rows, and I need to locate the second to last row in the table. HTML Code <table class="table"> <tr><td>1</td></tr> <tr><td>2</td>< ...

express-validator never accepts valid input

Currently, I am working on a project using the most recent version of nodejs and express. The basic site setup is complete, and now I am focusing on implementing user authentication based on what I've learned from this course. However, no matter what ...

Utilizing Angular's globally accessible variables

As we make the switch to Angular.js for our webapp, we are faced with the challenge of storing global data. In the past, we used a global object called app to store various functions and variables that needed to be accessed across different parts of the ap ...