简体   繁体   English

有条件地绑定vue js中的自定义指令,以“在元素事件外部单击”

[英]Conditionally bind custom-directives in vue js for 'clicking outside an element event'

References 参考文献

Add vue directive on condition 在条件上添加vue指令

Detect click outside element 检测点击外部元素

I am writing a custom directive for 'click-outside senario' for a list of elements. 我正在为元素列表“ click-outside senario”编写一个自定义指令。

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 come 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 accomplish 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>

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. 您不会像使用v-on那样获得自定义指令的自动“表达式/语句”绑定,这就是为什么要向其传递额外参数时需要处理处理函数的原因。

By this I mean the argument passed to v-on can be an expression or a statement, such as: 我的意思是传递给v-on的参数可以是表达式或语句,例如:

@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. 除非handler()返回另一个函数,否则上面的第二行是不可能的。


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. 没有办法有条件地启用/禁用指令,您将不得不执行类似Morty's answer的操作 ,两者都有些混乱。

This seems workable but the whole point of using custom directives is to write reusable dom manipulation code 这似乎可行,但是使用自定义指令的全部目的是编写可重用的dom操作代码

Are you against writing DOM manipulation code outside of directives? 您是否反对在指令之外编写DOM操作代码? Angular 1 had this philosophy. Angular 1拥有这种理念。 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 component". 除非您想在不同的情况下重用该指令,否则为这种情况编写一个指令以免“ DOM操作代码不会污染我的组件”可能是过大的杀伤力。 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 component inside a v-for and not a div and I bind the custom directive on that component of which the handler is a method 原因我在v-for而不是div内有一个组件,并且将自定义指令绑定到该处理程序是该方法的组件上

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. 您可以在已mounted钩子中注册主体单击事件,然后将其在destroyed钩子中删除。 All of the click-outside logic can be contained within the component 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. 你可以走一步,并自动添加身体click事件侦听器,只要handlers不为空,并删除侦听器时handlers是空的。 It's up to you. 由你决定。

Currently there is no easy way to do conditional directive binding. 当前,没有简单的方法可以进行条件指令绑定。 You might considered using v-if . 您可能考虑使用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. 另一种方法是修改指令的实现,以便它接受另一个活动的布尔标志以选择退出eventListener。

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM