Skip to content

Commit f3a57b2

Browse files
committed
Improvements to the text
1 parent 043e1bc commit f3a57b2

File tree

1 file changed

+42
-25
lines changed

1 file changed

+42
-25
lines changed

src/v2/cookbook/clickoutside-directive.md

Lines changed: 42 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -6,18 +6,20 @@ order: 2.1
66

77
## What we are building
88

9-
Many UI elements require to have to clicks that happen outside of them. Common usecases are:
9+
Many UI elements need to react to clicks that happen *outside* of them. Common usecases are:
1010

1111
* Modals
1212
* Dropdown menus
1313
* Popovers
1414
* Image Lightboxes
1515

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.
16+
We will build a small [Custom Directive](https://vuejs.org/v2/guide/custom-directive.html) that we can use in any component in need of 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 directive.
1717

18-
The result will look like this:
18+
> If you haven't already read the our Vue.js Guide about custom directives, we recommend you change this now. [Click here](../guide/custom-directive.html) to got to the relevant section in the guide.
1919
20-
``` html
20+
Ready? Great. So this is what the endresult of our efforts will look like:
21+
22+
```html
2123
<modal v-clickoutside="handler">
2224
<!--
2325
"handler" should be a method in your component.
@@ -26,36 +28,46 @@ The result will look like this:
2628
</modal>
2729
```
2830

29-
## Starting simple
31+
## bind(): Adding the desired behavior
3032

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+
The basic functionality is easy enough to achive. We register a new directive with Vue, and use the `bind()` hook to register an event listener on the document:
3334

34-
```JavaScript
35+
```javascript
3536
Vue.directive('clickoutside', {
3637
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-
})
38+
// get the "handler" function that gets passed to the directive.
39+
const handler = binding.value
40+
// some error checking
41+
if (typof handler !== 'functionality') {
42+
console.warn('[vue-clickoutside]: bind value must be a function.')
43+
return
44+
}
45+
46+
setTimeout(function() {
47+
document.addEventListener('click', function(event) {
48+
const target = event.target
49+
// check weither the click happened outside of el
50+
if (!el.contains(target) && el !== target) {
51+
handler(event)
52+
}
53+
})
54+
}, 0)
4455
}
4556
})
4657
```
4758

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.
59+
So what happened here?
5160

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.
61+
* When the directive is bound to the element, its `bind()` hook is called.
62+
* In this hook, we add an Event listener to the document, which will call the handler of the component for *any* click happening on our page.
63+
* We use `setTimeout()` because if we didn't, then a `click` event from a button that is responsible to make our modal appear could trigger our directive's handler immediatly, and we don't want that, of course.
64+
* And to make sure that this handler is only called when the click actually happened outside of out element `el`, we first check that the event's target wasn't `el` or one of its child nodes.
5365

54-
## Cleaning up after ourselves: Removing the Listener
66+
## unbind(): Cleaning up after ourselves
5567

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.
68+
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 our directive will be gone after this too - and so should the listener.
5769

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.
70+
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 because we don't have to touch the element, but requires a polyfill for older browsers. We will show you both ways here.
5971

6072
#### Saving the handler on the element
6173

@@ -67,15 +79,17 @@ Vue.directive('clickoutside', {
6779
// create a named function for the handler
6880
function handler(event) {
6981
const target = event.target
70-
if (!el.contains(target) && el === target) {
82+
if (!el.contains(target) && el !== target) {
7183
handler(event)
7284
}
7385
}
7486

7587
// and save it in a property on the element
7688
el.__vueClickOutside__ = handler
7789

78-
document.addEventListener('click', handler)
90+
setTimeout(function() {
91+
document.addEventListener('click', handler)
92+
}, 0)
7993
},
8094

8195
unbind(el) {
@@ -91,10 +105,13 @@ Vue.directive('clickoutside', {
91105
})
92106
```
93107

94-
#### Using a Map() to cache the handler
108+
#### Using a ES6 Map to cache the handler
95109

96110
```JavaScript
97111

112+
// we create the map outside of the directive,
113+
// because we only need one.
114+
// We don't have to create one for each instance of our directive.
98115
var handlerCache = new Map()
99116

100117
Vue.directive('clickoutside', {

0 commit comments

Comments
 (0)