admin管理员组

文章数量:1429844

References

Add vue directive on condition

Detect click outside element

I am writing a custom directive for 'click-outside senario' for a list of elements.

Basically when a button is clicked on a item in the list it goes into selected mode . Now if a click occurs anywhere else I need to cancel selection mode . For that I need to detect click outside . I figured out the directive for it from For that I have e up with

  const clickOutside = {
  bind: function (el, binding, vnode) {
    console.log('bind called')

    document.body.addEventListener('click', (event) => {
      // check that click was outside the el and his childrens
      if (!(el == event.target || el.contains(event.target))) {
        // and if it did, call handle method provided in attribute value
        console.log('directive working')
        vnode.context[binding.expression](event);
      }
    })
  },
  unbind: function (el) {
    document.body.removeEventListener('click', el.event)
    console.log('unbind called')
  }
}
export {
  clickOutside
}

from the reference sited above

Now I only want each list item to listen for outside clicks when that item is in selected mode .

So I need to acplish something like

<div id="list-item"  v-on-click-outside="outSideClickHandler" //trigger only when selected>
</div>

<script>
export default{
data:{
selectedState:false;
},
methods:{
outSideClickHandler:{//......}
}
</script>

References

Add vue directive on condition

Detect click outside element

I am writing a custom directive for 'click-outside senario' for a list of elements.

Basically when a button is clicked on a item in the list it goes into selected mode . Now if a click occurs anywhere else I need to cancel selection mode . For that I need to detect click outside . I figured out the directive for it from For that I have e up with

  const clickOutside = {
  bind: function (el, binding, vnode) {
    console.log('bind called')

    document.body.addEventListener('click', (event) => {
      // check that click was outside the el and his childrens
      if (!(el == event.target || el.contains(event.target))) {
        // and if it did, call handle method provided in attribute value
        console.log('directive working')
        vnode.context[binding.expression](event);
      }
    })
  },
  unbind: function (el) {
    document.body.removeEventListener('click', el.event)
    console.log('unbind called')
  }
}
export {
  clickOutside
}

from the reference sited above

Now I only want each list item to listen for outside clicks when that item is in selected mode .

So I need to acplish something like

<div id="list-item"  v-on-click-outside="outSideClickHandler" //trigger only when selected>
</div>

<script>
export default{
data:{
selectedState:false;
},
methods:{
outSideClickHandler:{//......}
}
</script>
Share Improve this question asked May 11, 2018 at 8:25 Sainath S.RSainath S.R 3,3069 gold badges42 silver badges73 bronze badges
Add a ment  | 

2 Answers 2

Reset to default 4

Why don't you just do the selected check inside the click outside handler? You'll also need a way of passing the clicked item to the handler.

<div id="list-item" v-on-click-outside="outSideClickHandler(item)"></div>
outSideClickHandler(item) {
  return event => {
    if (item.selected) {
      // Handle the click outside
    }
  };
}

Call the handler in the directive like this:

binding.value(event);

You do not get the automatic "expression/statement" binding for custom directives like do you with v-on which is why you need to curry the handler function when you want to pass extra arguments to it.

By this I mean the argument passed to v-on can be an expression or a statement, such as:

@click="handler"        - handler is an expression (the function itself)
@click="handler(item)"  - handler(item) is a statement

But with custom directives you can only pass expressions; the second line above is not possible unless handler() returns another function.


I think there is some confusion because it seems what you want is to have a custom directive which is used only in this specific situation with your list items, but my solution above is more about writing a general "click outside" directive which you can use in any situation.

Also I think you do not want the directive to register any event listeners if the list item is not selected (for performance reasons?). If that's the case, then you can use event delegation instead.

There is no way to conditionally enable/disable a directive, you would have to do something like Morty's answer, both of which is kind of messy.

This seems workable but the whole point of using custom directives is to write reusable dom manipulation code

Are you against writing DOM manipulation code outside of directives? Angular 1 had this philosophy. Unless you want to reuse the directive in different situations then it may be overkill to write a directive for this situation just so that "DOM manipulation code does not pollute my ponent". If I'm going to write a directive, then I would want it to be as general as possible so that I can use it in many different situations.

I don't even need to pass the item in that case. Cause I have a ponent inside a v-for and not a div and I bind the custom directive on that ponent of which the handler is a method

Then I'm not sure why you'd want to implement this as a directive, which is rarely needed anyway. You can just register the body click event in the mounted hook and remove it in the destroyed hook. All of the click-outside logic can be contained within the ponent itself.


If your main concern is not having to register a body click event listener for each list item, you can do something like this:

const handlers = new Map();

document.addEventListener('click', e => {
    for (const handler of handlers.values()) {
        handler(e);
    }
});

Vue.directive('click-outside', {
    bind(el, binding) {
        const handler = e => {
            if (el !== e.target && !el.contains(e.target)) {
                binding.value(e);
            }
        };

        handlers.set(el, handler);
    },

    unbind(el) {
        handlers.delete(el);
    },
});

You can go one step further and automatically add the body click event listener whenever handlers is nonempty and remove the listener when handlers is empty. It's up to you.

Currently there is no easy way to do conditional directive binding. You might considered using v-if.

   <div v-for='item of list'>
        <div
          v-if='item.selected'
          id="list-item" 
          v-on-click-outside="outSideClickHandler"
        />
        <div v-else
          id="list-item" 
          v-on-click-outside="outSideClickHandler"
        />
    </div>

Another approach would be modifying you directive implementation, so that it accepts another active boolean flag to opt out inside the eventListener.

<div id="list-item"  v-on-click-outside="{handler: outSideClickHandler, active: item.selected}"  />

本文标签: