Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

VueJs 2 custom directive to close when clicked outside not working

Tags:

vuejs2

I'm working on a vuejs component in which if you click outside of the combobox after you clicked on it, it's supposed to close.

My problem it's that the custom directive it's no working, the program compiles but in the browser i get this error:

[Vue warn]: Error in directive click-outside bind hook: "TypeError: Cannot set property 'event' of undefined"

This is the code of my component:

<template>
  <div class="form-group" v-click-outside="hide">
  <label v-if="label" for="combobox" class="control-label" v-tack>{{ label }}:</label>
    <input id="combobox"
    class="form-control combo-box-control"
    v-on:keyup="filter(searchText,options)"
    v-model="searchText"
    :placeholder="placeholder"
    v-on:click="showAllOptions()" :disabled="isDisabled">
        <template v-if="showAutocomplete">
        <div class="combobox-list">
            <p class="combobox-options" :key="item.id" v-for="item in listFiltered" v-on:click="optionSelected(item)">{{item.text}}</p>
        </div>
        </template>
  </div>
</template>
<script>
export default {
  data () {
    return {
      listFiltered: [],
      searchText: '',
      showAutocomplete: false
    }
  },
  props: {
    name: { type: String, required: true },
    options: Array,
    label: String,
    isDisabled: { type: Boolean, default: false },
    selectedOption: Object,
    placeholder: String
  },
  methods: {
    filter (word, array) {
      if (word === undefined) {
        this.showAutocomplete = false
        this.listFiltered = []
        return
      }
      this.showAutocomplete = true
      this.listFiltered = array.filter(function (item) {
        return item.text.toLowerCase().includes(word.toLowerCase())
      })
    },
    optionSelected (item) {
      this.searchText = item.text
      this.showAutocomplete = false
      if (item !== undefined) {
        this.$emit('change', { name: this.name, item: item })
      }
    },
    showAllOptions () {
      this.listFiltered = this.options
      this.showAutocomplete = !this.showAutocomplete
    },
    hide () {
      this.showAutocomplete = false
    }
  }
}
</script>
<style>
.form-group{
    position:relative;
}
.form-group input{
    width:100%;
}

input.combo-box-control:active{
  border-style: solid;
  border-width: 1px 1px 0 1px;
  border-radius: 5px 5px 0px 0px;
  border-color: #96c8da;
}

.form-control.combo-box-control:focus{
  border-color: #96c8da;
}

.combobox-list{
    position:relative;
    height:154px;
    width:100%;
    background-color:white;
    overflow-y:auto;
    text-align:justify;
    z-index: 5;
    border-style: solid;
    border-color: #96c8da;
    border-width: 0 1px 1px 1px;
    border-radius: 0px 0px 5px 5px;
    top: -3px;
}

.combobox-options{
    padding:6px 0;
    margin:0;
}
.combobox-options:hover{
    background-color:#d9d9d9;
}
</style>

and the main.js:

Vue.directive('click-outside', {
  bind: function (el, binding, vnode) {
    this.event = function (event) {
      if (!(el === event.target || el.contains(event.target))) {
        vnode.context[binding.expression](event)
      }
    }
    document.body.addEventListener('click', this.event)
  },
  unbind: function (el) {
    document.body.removeEventListener('click', this.event)
  }
})

the problem is in the line this.event = function (event) { because the error says Cannot set property 'event' of undefined, so this is undefined. Not sure how to define this, inside the directive.

I'm using this example to make it work with my custom component: https://jsfiddle.net/Linusborg/yzm8t8jq/ Did i missed something? Update: Turns out the code from the example is from a vuejs 1.x, although i found a similar example in vuejs 2.1 https://jsfiddle.net/y0rpfecd/, still get the same error.

like image 511
Progs Avatar asked Jan 28 '26 13:01

Progs


1 Answers

You may have found answer to it. Hope somebody find it useful.

I recently tested click-outside directive in my side project. Just replace this.event with window.event in directive code. And it works perfect!!

directive.js

import Vue from 'vue';

Vue.directive('click-outside', {
    bind: function (el, binding, vnode) {
        window.event = function (event) {
            if (!(el == event.target || el.contains(event.target))) {
                vnode.context[binding.expression](event);
            }
        };
        document.body.addEventListener('click', window.event)
    },
    unbind: function (el) {
        document.body.removeEventListener('click', window.event)
    },
});

To use in component: Just import directive.

<template>
  <div class="form-group" v-click-outside="hide">
  <label v-if="label" for="combobox" class="control-label" v-tack>{{ label }}:</label>
    <input id="combobox"
    class="form-control combo-box-control"
    v-on:keyup="filter(searchText,options)"
    v-model="searchText"
    :placeholder="placeholder"
    v-on:click="showAllOptions()" :disabled="isDisabled">
        <template v-if="showAutocomplete">
        <div class="combobox-list">
            <p class="combobox-options" :key="item.id" v-for="item in listFiltered" v-on:click="optionSelected(item)">{{item.text}}</p>
        </div>
        </template>
  </div>
</template>
<script>
import clickOutside from '../directive';
export default {
  data () {
    return {
      listFiltered: [],
      searchText: '',
      showAutocomplete: false
    }
  },
  props: {
    name: { type: String, required: true },
    options: Array,
    label: String,
    isDisabled: { type: Boolean, default: false },
    selectedOption: Object,
    placeholder: String
  },
  methods: {
    filter (word, array) {
      if (word === undefined) {
        this.showAutocomplete = false
        this.listFiltered = []
        return
      }
      this.showAutocomplete = true
      this.listFiltered = array.filter(function (item) {
        return item.text.toLowerCase().includes(word.toLowerCase())
      })
    },
    optionSelected (item) {
      this.searchText = item.text
      this.showAutocomplete = false
      if (item !== undefined) {
        this.$emit('change', { name: this.name, item: item })
      }
    },
    showAllOptions () {
      this.listFiltered = this.options
      this.showAutocomplete = !this.showAutocomplete
    },
    hide () {
      this.showAutocomplete = false
    }
  }
}
</script>
<style>
.form-group{
    position:relative;
}
.form-group input{
    width:100%;
}

input.combo-box-control:active{
  border-style: solid;
  border-width: 1px 1px 0 1px;
  border-radius: 5px 5px 0px 0px;
  border-color: #96c8da;
}

.form-control.combo-box-control:focus{
  border-color: #96c8da;
}

.combobox-list{
    position:relative;
    height:154px;
    width:100%;
    background-color:white;
    overflow-y:auto;
    text-align:justify;
    z-index: 5;
    border-style: solid;
    border-color: #96c8da;
    border-width: 0 1px 1px 1px;
    border-radius: 0px 0px 5px 5px;
    top: -3px;
}

.combobox-options{
    padding:6px 0;
    margin:0;
}
.combobox-options:hover{
    background-color:#d9d9d9;
}
</style>
like image 63
Swati Avatar answered Feb 01 '26 17:02

Swati



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!