将焦点集中在与在Angular中单击其他元素冲突的元素上

时间:2018-08-27 10:38:25

标签: angular click focusout

我在focusout()上发生了element1事件,在click()上发生了element2事件,并且当element1失去焦点是因为在{ {1}},仅触发聚焦,而没有单击事件。

[在jQuery] [1]上工作正常,但在Angular中效果不佳。

我找到了解决方法,方法是添加一个element2,它也适用于角度。不幸的是我做不到。

非常感谢另一个建议。

请使用setTimeout查找代码:


 


window.setTimeout()

2 个答案:

答案 0 :(得分:4)

点击事件有问题。

点击事件包含2个事件,mousedown和mouseup。

您所遇到的事件的顺序是这样

1)按下鼠标 2)聚焦 3)mouseup

在1和3发生点击事件的地方。

当页面上显示其他元素(例如错误消息)并且应该在其上进行单击的按钮从其原始x和y坐标移动时,可能会发生这种情况。因此,mouseup发生在其他地方,而不是发生mousedown的地方。

基本上,我认为您的鼠标按下有效,焦点对准有效,而mouseup无效。

解决方案是使用mousedown事件而不是click。因此,您的点击不应该等待mouseup起作用。

示例:

<input type="text" (focusout)="someMethod()">
<button (mousedown)="someMethod()">Click Me!</button> //Changed (click) to (mousedown)

希望这会有所帮助。

答案 1 :(得分:0)

我开发了一种不需要'setTimeout'的解决方案,并且不会强迫您使用'mouseup'事件而不是click事件。这是用户友好的,因为单击事件“通过释放鼠标之前将鼠标从按钮上移开,使用户有机会中止单击”。 (通过野餐发表评论)

问题

answer by Vinod中所述,这是事件发生时间上的问题:

  1. mousedown:该按钮注册一个鼠标按下事件。
  2. focusout:由于鼠标左键按下而注册。在这种独特的情况下,焦点处理程序使按钮移到另一个位置。
  3. mouseup:由于按钮的位置已更改,因此不会注册mouseup事件。因此,单击事件也未注册,因为这需要在同一元素上先按下鼠标然后再按下鼠标。

解决方案

我的解决方案是一条指令,该指令公开在mousedown和mouseup事件之后发生的延迟对焦事件。。因此,在(延迟的)focusout事件的事件处理程序更改了click事件之前,将注册click事件。按钮的位置。

这由BehaviourSubject完成,它存储鼠标当前是否处于按下状态。当鼠标按下时注册聚焦事件时,我们不会立即触发延迟的聚焦事件(否则我们将遇到同样的老问题)。相反,我们等待鼠标再次返回,然后发出延迟的聚焦事件。这导致以下顺序:

  1. 鼠标按下
  2. 聚焦(忽略此事件)
  3. 鼠标
  4. 延迟对焦+单击

代码解决方案

该指令的用法如下:

<input appDelayedFocusout (delayedFocusout)="yourLayoutChangingHandler()">

我的指令实现利用until-destroy库来防止内存泄漏永远不会终止订阅,但可以随时进行修改。

import {Directive, EventEmitter, HostListener, OnInit, Output} from '@angular/core';
import {BehaviorSubject, fromEvent} from 'rxjs';
import {UntilDestroy, untilDestroyed} from '@ngneat/until-destroy';
import {filter, map, take} from 'rxjs/operators';

/**
 * This directive exposes a special variant of the 'focusout' event. The regular 'focusout' event has a quirk:
 * Imagine the user clicks on some button on the page. This triggers the following events in the following order:
 * mousedown, focusout, mouseup. But the focusout event handler might change the layout of the website so that
 * the button on which the mousedown event occurred moves around. This leads to no mouseup event registered on
 * that button. Therefore a click event is also not registered because a click event consists of
 * a mousedown AND a mouseup event on that button. In order to fix that problem, this directive exposes a delayed focusout
 * event that is triggered AFTER the mousedown and mouseup events. When the delayed focusout event handler changes
 * positions of buttons, click events are still registered as you would expect.
 */
@UntilDestroy()
@Directive({
  selector: '[appDelayedFocusout]'
})
export class DelayedFocusoutDirective implements OnInit {
  @Output() delayedFocusout = new EventEmitter<boolean>();
  isMouseDownSubject = new BehaviorSubject(false);

  ngOnInit(): void {
    fromEvent(document.body, 'mousedown').pipe(untilDestroyed(this))
      .subscribe(() => this.isMouseDownSubject.next(true));
    fromEvent(document.body, 'mouseup').pipe(untilDestroyed(this))
      .subscribe(() => this.isMouseDownSubject.next(false));
  }

  @HostListener('focusout') onFocusout() {
    // If the mouse is currently down, we subscribe to the the event of
    // 'mouse being released' to then trigger the delayed focusout.
    // If the mouse is currently not down, we can trigger the delayed focusout immediately.
    if (this.isMouseDown()) {
      this.mouseRelease().subscribe(() => {
        // This code is executed once the mouse has been released.
        this.delayedFocusout.emit(true);
      });
    } else {
      this.delayedFocusout.emit(true);
    }
  }

  /**
   * Emits the value true once the mouse has been released and then completes.
   * Also completes when the mouse is not released but this directive is being destroyed.
   */
  mouseRelease() {
    return this.isMouseDownSubject.pipe(
      untilDestroyed(this),
      // Just negate isDown to get the value isReleased.
      map(isDown => !isDown),
      // Only proceed when the the mouse is released.
      filter(isReleased => isReleased),
      take(1)
    );
  }

  isMouseDown() {
    return this.isMouseDownSubject.value;
  }
}