Reputation: 869
Background
I created a custom directive (to detect clicks outside of an element) by following this guide: https://tahazsh.com/detect-outside-click-in-vue.
Element:
<button
id='burger'
ref='burger'
class='burger button is-white'>
<span class='burger-top' aria-hidden='true' style='pointer-events: none;'></span>
<span class='burger-bottom' aria-hidden='true' style='pointer-events: none;'></span>
</button>
Directive:
import Vue from 'vue';
let handleOutsideClick;
Vue.directive('outside', {
bind(el, binding, vnode) {
setTimeout(() => {
handleOutsideClick = (e) => {
e.stopPropagation();
const { handler, exclude } = binding.value;
let clickedOnExcludedEl = false;
exclude.forEach((refName) => {
if (!clickedOnExcludedEl) {
const excludedEl = vnode.context.$refs[refName]
console.log(vnode.context.$refs);
clickedOnExcludedEl = excludedEl.contains(e.target);
}
});
if (!el.contains(e.target) && !clickedOnExcludedEl) {
vnode.context[handler]();
}
};
document.addEventListener('click', handleOutsideClick);
document.addEventListener('touchstart', handleOutsideClick);
}, 50);
},
unbind() {
document.removeEventListener('click', handleOutsideClick);
document.removeEventListener('touchstart', handleOutsideClick);
},
});
Note that I have had to add setTimeout
.
Using the directive in a sidebar component:
<div
id='sidebar'
class='sidebar-container'
v-show='toggleSideBar'
v-outside='{
handler: "close",
exclude: [
"burger",
],
}'>
<div class='sidebar-content'>
// other content
</div>
</div>
Problem
$refs
is empty, even though the element clearly has a ref
.
This is clear also from console.log(vnode.context.$refs);
.
Notably, getting the element by id works:
exclude.forEach((id) => {
if (!clickedOnExcludedEl) {
const excludedEl = document.getElementById(id);
clickedOnExcludedEl = excludedEl.contains(e.target);
}
});
Question
Why is $refs
empty when the element clearly exists and can be accessed by id?
Upvotes: 7
Views: 2445
Reputation: 4464
It is so, because your ref
is not what refName
is.
Your ref is "burger" but refName is a exclude
property given to v-closable
which you then restructure.
In the tutorial you linked to, refName
is button
. So I'm assuming it is the same in your code.
Make sure both ref="burger"
and
v-closable="{ exclude: ['burger'], // this is your refName handler: 'onClose' }"
are the same.
Upvotes: 2
Reputation: 29092
The way this code is currently written the ref='burger'
and v-outside
must be in the same template.
The key line is:
const excludedEl = vnode.context.$refs[refName]
vnode.context
will refer to the Vue instance for your sidebar component. The $refs
must be defined in the template for that component.
$refs
are local to a particular Vue instance. By 'Vue instance' I mean the component instance, if that terminology is any clearer. So the sidebar will have one set of $refs
and the navbar will have its own set of $refs
.
Based on the comment under the question it would seem that the button is currently defined in the template for the navbar component. So you'll end up with a burger
ref in the navbar's $refs
but the directive is looking for it inside the sidebar's $refs
.
The way that directive is written needs a template a bit like this:
<div>
<div
v-outside='{
handler: "close",
exclude: ["burger"]
}'
>
...
</div>
<button ref='burger'>...</button>
</div>
Here the v-outside
and ref='burger'
are both in the same template so everything will work fine. As soon as you move the button to a different template it won't be able to find it.
Using an id
and document.getElementById
is a very different scenario. Element ids are registered globally. It doesn't matter where the element is in the DOM, so long as it is present it will be found.
Upvotes: 4