Saturday 7 September 2019

Directive not responding due to same mouse events being used in multiple directives on same element

I have a feature where box can be dragged and dropped into the grey area(Please refer to the stackblitz link) Once the box is dragged and dropped , the box can only be moved within the grey area by clicking on the pink color of the box.

Resizing functionality has also been added, so the box can be resized.

Before resize directive was added the box can only be moved within grey area but after adding adding the resize directive when we resize, the box starts moving out of the grey area , the issue is the box should not move out of the grey area when resizing is done

Stackblitz link

https://stackblitz.com/edit/angular-kkj1wc?file=src/app/hello.component.html

hello.component.html [ has the box and directives applied on the box ]

<div appMovableArea appDropzone (drop)="move(currentBox, dropzone1)">
<div *ngFor="let box of dropzone1"
      appDroppable
     (dragStart)="currentBox = box"
      appMovable
      resize>
    
</div>
</div>

where the box has (dragStart) output event emitter which is bound to draggable directive([appDroppable + draggable] and for the move functionality appMovable, appMovableArea directives are there) .And the events are shared aming directives using Droppable.service.ts

The (drop) is an output event emitter applied on grey area using dropzone[appDropzone] directive

import { Directive, ElementRef, EventEmitter, HostBinding, HostListener, 
    OnInit, Output, SkipSelf } from '@angular/core';
import { DroppableService } from './droppable.service';

@Directive({
     selector: '[appDropzone]',
     providers: [DroppableService]
    })
export class DropzoneDirective implements OnInit {

     @Output() drop = new EventEmitter<PointerEvent>();
     @Output() remove = new EventEmitter<PointerEvent>();
     private clientRect: ClientRect;

constructor(@SkipSelf() private allDroppableService: DroppableService,
              private innerDroppableService: DroppableService,
              private element: ElementRef) { }

ngOnInit(): void {
        this.allDroppableService.dragStart$.subscribe(() => 
        this.onDragStart());
        this.allDroppableService.dragEnd$.subscribe(event => 
        this.onDragEnd(event));

        this.allDroppableService.dragMove$.subscribe(event => {
          if (this.isEventInside(event)) {
        this.onPointerEnter();
      } else {
        this.onPointerLeave();
      }
     });
        this.innerDroppableService.dragStart$.subscribe(() => 
        this.onInnerDragStart());
        this.innerDroppableService.dragEnd$.subscribe(event => 
        this.onInnerDragEnd(event));
}

private onPointerEnter(): void {
     if (!this.activated) {
      return;
     }
     this.entered = true;
     }

private onPointerLeave(): void {
        if (!this.activated) {
         return;
      }
     this.entered = false;
     }

private onDragStart(): void {
       this.clientRect = this.element.nativeElement.getBoundingClientRect();
       this.activated = true;
      }

private onDragEnd(event: PointerEvent): void {
        if (!this.activated) {
         return;
      }
      if (this.entered) {
        this.drop.emit(event);
      }
     }

private onInnerDragStart() {
    this.activated = true;
    this.entered = true;
    }

private onInnerDragEnd(event: PointerEvent) {
    if (!this.entered) {
      this.remove.emit(event);
    }
  }

private isEventInside(event: PointerEvent) {
    return event.clientX >= this.clientRect.left &&
      event.clientX <= this.clientRect.right &&
      event.clientY >= this.clientRect.top &&
      event.clientY <= this.clientRect.bottom;
  }
}

Then on the box (dragStart) output event emitter which is present indraggable directive[appDraggable] which listens for pointerdown events

import { Directive, EventEmitter, HostBinding, HostListener, Output, 
ElementRef } from '@angular/core';
@Directive({
 selector: '[appDraggable],[appDroppable]'
})
export class DraggableDirective {
 @Output() dragStart = new EventEmitter<PointerEvent>();
 @Output() dragMove = new EventEmitter<PointerEvent>();
 @Output() dragEnd = new EventEmitter<PointerEvent>();

 constructor(public element: ElementRef) {}

 @HostListener('pointerdown', ['$event'])
   onPointerDown(event: PointerEvent): void {
    if (event.button !== 0) {
    return;
   }
   this.pointerId = event.pointerId;
   this.dragging = true;
   this.dragStart.emit(event); 
   }

 @HostListener('document:pointermove', ['$event'])
    onPointerMove(event: PointerEvent): void {
     if (!this.dragging || event.pointerId !== this.pointerId) {
     return;
    } 
    this.dragMove.emit(event);
    }

 @HostListener('document:pointercancel', ['$event'])
 @HostListener('document:pointerup', ['$event'])
     onPointerUp(event: PointerEvent): void {
    if (!this.dragging || event.pointerId !== this.pointerId) {
      return;
    }
    this.dragging = false;
    this.dragEnd.emit(event);
    }
   }

Movable directive for maintaining move inside the grey area which in turn uses calculation based from movable-area directive

import { Directive, ElementRef, HostBinding, HostListener, Input } from 
'@angular/core';
import { DraggableDirective } from './draggable.directive';
import { DomSanitizer, SafeStyle } from '@angular/platform-browser';

interface Position {
 x: number;
 y: number;
}

@Directive({
 selector: '[appMovable]'
})
export class MovableDirective extends DraggableDirective {
  @HostBinding('style.transform') get transform(): SafeStyle {
   return this.sanitizer.bypassSecurityTrustStyle(
  `translateX(${this.position.x}px) translateY(${this.position.y}px)`
  );
  }

  @HostBinding('class.movable') movable = true;
  position: Position = {x: 0, y: 0};
  private startPosition: Position;
  @Input('appMovableReset') reset = false;

constructor(private sanitizer: DomSanitizer, public element: ElementRef) {
   super(element);
 }

@HostListener('dragStart', ['$event'])
   onDragStart(event: PointerEvent) {
     this.startPosition = {
     x: event.clientX - this.position.x,
     y: event.clientY - this.position.y
 }
 }

@HostListener('dragMove', ['$event'])
   onDragMove(event: PointerEvent) {
    this.position.x = event.clientX - this.startPosition.x;
    this.position.y = event.clientY - this.startPosition.y;
 }

@HostListener('dragEnd', ['$event'])
   onDragEnd(event: PointerEvent) {
    if (this.reset) {
     this.position = {x: 0, y: 0};
 }
 }
}



from Directive not responding due to same mouse events being used in multiple directives on same element

No comments:

Post a Comment