簡體   English   中英

Vue.js 指令插入/綁定覆蓋 eventListener 點擊事件

[英]Vue.js Directive inserted/bind overwrites eventListener click event

假設我在頁面上有多個下拉菜單或元素,它們都使用了我用過的稱為 closable 的指令。 如果單擊的元素在使用指令的元素之外,這將調用傳入的表達式。

然而,預期的行為是,如果我單擊頁面上的一個元素,即另一個帶有指令的下拉列表,它應該得到該單擊事件路徑,將它們與現有的進行比較,如果它們不匹配或不包含在元素中,它應該關閉它。

實際發生的是點擊事件從未被注冊,它只是初始化了另一個指令並且由於某種原因點擊事件丟失了。

唯一一次注冊點擊事件是我點擊了沒有指令的東西。

Vue.directive ( 'closable', {
    inserted: ( el, binding, vnode ) => {
        // assign event to the element
        el.clickOutsideEvent = function ( event ) {
            console.log ( {el, event} );
            // here we check if the click event is outside the element and it's children
            if ( !( el == event.path[0] || el.contains ( event.path[0] ) ) ) {
                // if clicked outside, call the provided method
                vnode.context[binding.expression] ( event );
            }
        };
        // register click and touch events
        document.body.addEventListener ( 'click', el.clickOutsideEvent );
        document.body.addEventListener ( 'touchstart', el.clickOutsideEvent );
    },
    unbind: function ( el ) {
        // unregister click and touch events before the element is unmounted
        document.body.removeEventListener ( 'click', el.clickOutsideEvent );
        document.body.removeEventListener ( 'touchstart', el.clickOutsideEvent );
    },
    stopProp ( event ) {
        event.stopPropagation ();
    },
} );

更新

這是v-click-outside指令的另一種變體 - 在本地,就在您的組件內部:

  directives:
    {
      clickOutside:
        {
          bind(elem, binding, vnode)
          {
            elem.clickOutsideEvent = function(evt)
            {
              if (elem !== evt.target && !elem.contains(evt.target)) vnode.context[binding.expression](evt);
            };
            document.body.addEventListener('click', elem.clickOutsideEvent);
          },
          unbind(elem)
          {
            document.body.removeEventListener('click', elem.clickOutsideEvent);
          }
        }
    },

你可以試試這個實現:

import Vue from 'vue'

const HAS_WINDOWS = typeof window !== 'undefined';
const HAS_NAVIGATOR = typeof navigator !== 'undefined';
const IS_TOUCH = HAS_WINDOWS && ('ontouchstart' in window || (HAS_NAVIGATOR && navigator.msMaxTouchPoints > 0));
const EVENTS = IS_TOUCH ? ['touchstart'] : ['click'];
const IDENTITY = (item) => item;

const directive = {
  instances: [],
};

function processDirectiveArguments (bindingValue)
{
  const isFunction = typeof bindingValue === 'function';
  if (!isFunction && typeof bindingValue !== 'object')
  {
    throw new Error('v-click-outside: Binding value must be a function or an object')
  }

  return {
    handler: isFunction ? bindingValue : bindingValue.handler,
    middleware: bindingValue.middleware || IDENTITY,
    events: bindingValue.events || EVENTS,
    isActive: !(bindingValue.isActive === false),
  }
}

function onEvent ({ el, event, handler, middleware })
{
  const isClickOutside = event.target !== el && !el.contains(event.target);

  if (!isClickOutside)
  {
    return
  }

  if (middleware(event, el))
  {
    handler(event, el)
  }
}

function createInstance ({ el, events, handler, middleware })
{
  return {
    el,
    eventHandlers: events.map((eventName) => ({
      event: eventName,
      handler: (event) => onEvent({
        event,
        el,
        handler,
        middleware
      }),
    })),
  }
}

function removeInstance (el)
{
  const instanceIndex = directive.instances.findIndex((instance) => instance.el === el);
  if (instanceIndex === -1)
  {
    // Note: This can happen when active status changes from false to false
    return
  }

  const instance = directive.instances[instanceIndex];

  instance.eventHandlers.forEach(({ event, handler }) =>
    document.removeEventListener(event, handler)
  );

  directive.instances.splice(instanceIndex, 1)
}

function bind (el, { value })
{
  const { events, handler, middleware, isActive } = processDirectiveArguments(value);

  if (!isActive)
  {
    return
  }

  const instance = createInstance({
    el,
    events,
    handler,
    middleware
  });

  instance.eventHandlers.forEach(({ event, handler }) =>
    setTimeout(() => document.addEventListener(event, handler), 0)
  );
  directive.instances.push(instance)
}

function update (el, { value, oldValue })
{
  if (JSON.stringify(value) === JSON.stringify(oldValue))
  {
    return
  }

  const { events, handler, middleware, isActive } = processDirectiveArguments(value);

  if (!isActive)
  {
    removeInstance(el);
    return
  }

  let instance = directive.instances.find((instance) => instance.el === el);

  if (instance)
  {
    instance.eventHandlers.forEach(({ event, handler }) =>
      document.removeEventListener(event, handler)
    );
    instance.eventHandlers = events.map((eventName) => ({
      event: eventName,
      handler: (event) => onEvent({
        event,
        el,
        handler,
        middleware
      }),
    }))
  }
  else
  {
    instance = createInstance({
      el,
      events,
      handler,
      middleware
    });
    directive.instances.push(instance)
  }

  instance.eventHandlers.forEach(({ event, handler }) =>
    setTimeout(() => document.addEventListener(event, handler), 0)
  )
}

directive.bind = bind;
directive.update = update;
directive.unbind = removeInstance;

Vue.directive('click-outside', directive); 

因此,在嘗試讓事件注冊之后,我決定以不同的方式向 go 發送電子郵件。

每次插入可關閉指令時,它都會調用之前打開的任何先前表達式,然后將新表達式處理程序添加到名為 prevNodes 的變量中,因此下次插入可關閉指令時,它會調用該表達式

let prevNodes = [];
Vue.directive ( 'closable', {
    inserted: ( el, binding, vnode ) => {

        console.log ( {prevNodes} );

        prevNodes.forEach ( item => {
            //console.log ( item );
            const {vnode, binding} = item;
            vnode.context[binding.expression] ();
        } );


        // assign event to the element
        el.clickOutsideEvent = function ( event ) {
            // here we check if the click event is outside the element and it's children
            if ( !( el == event.path[0] || el.contains ( event.path[0] ) ) ) {
                // if clicked outside, call the provided method
                vnode.context[binding.expression] ( event );
            }
        };

        prevNodes.push ( {vnode, binding} );

        // register click and touch events
        document.body.addEventListener ( 'click', el.clickOutsideEvent );
        document.body.addEventListener ( 'touchstart', el.clickOutsideEvent );
    },
    unbind: function ( el, binding, vnode ) {
        const removeIndex = prevNodes.findIndex ( item => item.vnode.elm === vnode.elm );
        prevNodes.splice ( removeIndex, 1 );
        // unregister click and touch events before the element is unmounted
        document.body.removeEventListener ( 'click', el.clickOutsideEvent );
        document.body.removeEventListener ( 'touchstart', el.clickOutsideEvent );
    },
    stopProp ( event ) {
        event.stopPropagation ();
    },
} );

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM