Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Angular 7 | HostListener blur conditional stop all other event callbacks

I have one requirement where We want the user to resolve input field errors and until that is done user is not allowed to do any other operation on-screen.

For the same, I have implemented HostListener on the input field and onBlur I am setting the focus back to the input field if there are validation scenarios fail.

And, i am doing e.preventDefault() and e.stopPropagtion() to stop all other event callback to be executed on the page (as after setting focus back, blur will be the first event to be executed).

But somehow on any external event, blur does get executed but it is not restricting other events to be executed. Those are also getting executing without which I am not able to achieve the desired functionality.

import { Directive, HostListener, Input } from '@angular/core';

@Directive({
  selector: '[appNumberFormat]'
})
export class NumberFormatDirective {

  constructor() { }

  @HostListener('blur', ['$event']) blur(evt) {
    evt.preventDefault();
    console.log('field focus...');
    evt.target.focus();
    return false;
    // if (evt.target.value.trim() === '') {
    //     this.formControl.control.setValue(this.defaultValue);
    // }
  }
}

I have replicated the same scenario in stackBlitz. Please have a look.

https://stackblitz.com/edit/angular-54ermg

like image 529
Manish Kumar Avatar asked Sep 19 '19 13:09

Manish Kumar


3 Answers

Blur isn't preventing another event because it's running independently. Setting pointer-events to none will prevent clicking.

onBlur(e) {
    e.preventDefault();
    e.stopPropagation();
    document.body.style.pointerEvents = 'none';
    console.log('field focus...');
    setTimeout(() => {
      e.target.focus();
    }, 10);

    setTimeout(() => {
      document.body.style.pointerEvents = 'initial';
    }, 300);

    return;
  }

You can also add an overlay when input is focused instead of using pointer-events: none. In both cases, you will need to handle tab presses.

In order to prevent tab + enter add on the input element:

(keydown)="onKeyDown($event)" 

In component ts:

onKeyDown(event) {
    if (event.code === 'Tab') {
      event.preventDefault();
      event.stopPropagation();
    }
  }
like image 51
RandomCode Avatar answered Oct 16 '22 17:10

RandomCode


One way you could do is disable all the event on the page using this solution (and make a custom for keypress that do not allow enter key).

Stop propagation for all events

  disableAllUserEvents = () => {
    const events = ["click", "contextmenu", "dblclick", "mousedown", "mouseenter", "mouseleave", "mousemove",
        "mouseover", "mouseout", "mouseup", "blur", "change", "focus", "focusin",
        "focusout", "input", "invalid", "reset", "search", "select", "submit", "drag", "dragend", "dragenter",
        "dragleave", "dragover", "dragstart", "drop", "copy", "cut", "paste", "mousewheel", "wheel", "touchcancel",
        "touchend", "touchmove", "touchstart"];

    const handler = event => {
      event.stopPropagation();
      event.preventDefault();

      return false;
  };

    for (let i = 0, l = events.length; i < l; i++) {
        document.addEventListener(events[i], handler, true);
    }

    return () => {
        for (let i = 0, l = events.length; i < l; i++) {
            document.removeEventListener(events[i], handler, true);
        }
    };
  };

and re enable the event when all is ok. It's a bit brute force, but it does work.

https://stackblitz.com/edit/angular-68vdrq

like image 2
Crocsx Avatar answered Oct 16 '22 18:10

Crocsx


I don't think it's an ideal way to achieve the required functionality. I think other buttons/anchor-links should remain disabled when there are errors in the form. You can bind the disabled property for both buttons & anchor-links to a function which will return true/false after checking the validations.

Nevertheless, browser executes the events in a specific order and if you need to stop the click event from progressing, you should return false. I've implemented the same with the use of a boolean variable which will set to true/false on based on validation checks, I've created a fork of your code here - https://stackblitz.com/edit/angular-uay8rm

  preventOtherActions: boolean = true;
  txtBoxValue: string = "";

  submitForm($event) {
    if(this.preventOtherActions) {
      return false;
    }
    console.log('submitForm');
  }

  onBlur(e) {
    console.log('field focus...');
    setTimeout(() => {
       e.target.focus();
      }, 10);
    this.preventOtherActions = this.txtBoxValue.length < 6;    
    return;
  }
like image 1
Hemendra Avatar answered Oct 16 '22 17:10

Hemendra