8000 batman! · ywwhack/vuejs.org@c45d160 · GitHub
[go: up one dir, main page]

Skip to content

Commit c45d160

Browse files
committed
batman!
1 parent d6c323f commit c45d160

File tree

1 file changed

+117
-0
lines changed

1 file changed

+117
-0
lines changed
Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
---
2+
title: A clickoutside directive
3+
type: cookbook
4+
order: 1.2
5+
---
6+
7+
## What we are building
8+
9+
Many UI elements require to have to clicks that happen outside of them. Common usecases are:
10+
11+
* Modals
12+
* Dropdown menus
13+
* Popovers
14+
* Image Lightboxes
15+
16+
We will build a small [Custom Directive](https://vuejs.org/v2/guide/custom-directive.html) that we can use in any component that needs this behaviour. One such component is the [modal example](https://vuejs.org/v2/examples/modal.html) from the Vue.js Website, so we will use this as a base component to demonstrate the usage of our new custom directive.
17+
18+
The result will look like this:
19+
20+
``` html
21+
<modal v-clickoutside="handler">
22+
<!--
23+
"handler" should be a method in your component.
24+
It will be called when the user clicks outside of the modal window
25+
-->
26+
</modal>
27+
```
28+
29+
## Starting simple
30+
31+
The basic functionality is easy enough to achive. We register a new directive with Vue, and use
32+
the `bind()` hook to register an event listener on the document:
33+
34+
```JavaScript
35+
Vue.directive('clickoutside', {
36+
bind(el, binding) {
37+
const handler = binding.value // this gives us the "handler" function the component passed to the directive.
38+
document.addEventListener('click', function(event) {
39+
const target = event.target
40+
if (!el.contains(target) && el === target) {
41+
handler(event)
42+
}
43+
})
44+
}
45+
})
46+
```
47+
48+
So what happened here? When the directive is bound to the element we defined it on, its `bind()` hook is called.
49+
50+
In this hook, we add an Event listener to the document, which will call the handler of the component on click.
51+
52+
And to make sure that this handler is only called when the click actually happened outside of out element `el`, we first weither the event's target was `el` or one of its child nodes.
53+
54+
## Cleaning up after ourselves: Removing the Listener
55+
56+
Our directive does the job, but it still has a flaw: We have no mechanism in place to remove the listener again if `el` is removed from the DOM. This is problematic because the usual usecase of our directive on a `<modal>`will be to close a modal, for example, so the listener should be gone after that, too.
57+
58+
We can correct that with the `unbind()` hook, but there's a catch we have to work around: since directives don't have instances, we can't save the handler method on it. To solve this challenge, we have two possibilities: we can either cache the hander on the element, or we can use a ES6 [Map](https://developer.mozilla.org/de/docs/Web/JavaScript/Reference/Global_Objects/Map). Using the latter is much cleaner, but requires a polyfill for older browsers. We will show you both ways here.
59+
60+
#### Saving the handler on the element
61+
62+
```JavaScript
63+
Vue.directive('clickoutside', {
64+
bind(el, binding) {
65+
const handler = binding.value
66+
67+
// create a named function for the handler
68+
function handler(event) {
69+
const target = event.target
70+
if (!el.contains(target) && el === target) {
71+
handler(event)
72+
}
73+
}
74+
75+
// and save it in a property on the element
76+
el.__vueClickOutside__ = handler
77+
78+
document.addEventListener('click', handler)
79+
},
80+
81+
unbind(el) {
82+
83+
if (el.__vueClickOutside__) {
84+
// retrieve the handler from the element
85+
const handler = el.__vueClickOutside__
86+
// and remove it from the document's click listeners
87+
document.removeEventListener('click', handler)
88+
}
89+
90+
}
91+
})
92+
```
93+
94+
#### Using a Map() to cache the handler
95+
96+
```JavaScript
97+
98+
var handlerCache = new Map()
99+
100+
Vue.directive('clickoutside', {
101+
bind(el, binding) {
102+
// ...
103+
// el.__vueClickOutside__ = handler
104+
handlerCache.set(el, handler)
105+
// ...
106+
},
107+
unbind(el) {
108+
109+
if (handlerCache.has(el)) {
110+
// get the handler from the Map
111+
const hander = handlerCache.get(el)
112+
document.removeEventListener('click', handler)
113+
}
114+
// ...
115+
}
116+
})
117+
```

0 commit comments

Comments
 (0)
0