javascript - Conditionally bind custom-directives in vue js for 'clicking outside an element event' - Stack Over

References Add vue directive on conditionDetect click outside elementI am writing a custom directive fo

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}"  />

发布者:admin,转转请注明出处:http://www.yc00.com/questions/1745478397a4629451.html

相关推荐

发表回复

评论列表(0条)

  • 暂无评论

联系我们

400-800-8888

在线咨询: QQ交谈

邮件:admin@example.com

工作时间:周一至周五,9:30-18:30,节假日休息

关注微信