VueJs 2 custom directive closes when outside click doesn't work
I'm working on a vuejs component that should close if you click outside of a combobox.
My problem is that the custom directive doesn't work, 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"
Here 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 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, which is undefined this.event = function (event) {
as the error Cannot set property 'event' of undefined
says. Not sure how to define it inside the directive.
I'm using this example to get it to work with my custom component: https://jsfiddle.net/Linusborg/yzm8t8jq/ Am I missing something? Update: The code in the original example is from vuejs 1.x, although I found a similar example in vuejs 2.1 https://jsfiddle.net/y0rpfecd/ I am still getting the same error.
You may have found the answer. Hope someone finds it useful.
I recently tested the click-outside directive in a side project. Just replace this.event with window.event in the directive code. And it works great!
instruction
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 a component: just import the 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>