Janco Boscan
Janco Boscan

Reputation: 195

How can I have a better MouseEvent implementation in my Component with Angular 4?

I have this working Plunker Example, you can drag the mouse and select the cards.

My problem is that the code has alot of bugs and I want to do something similar to this Demo

Here something that I want to fix (img1 and img2 - the cards in img #2 were clicked before), another thing is that if you drag the mouse fast sometimes the cards will not be select.

Here is my component code

export class App {

  private dragStart:number = 0;
  private dragOver:number = 0;
  public users:Array<{id?: number; name: string; admin: boolean;}> = [
        { name: 'Alexis Wursten', admin: false  },
        { name: 'Janco Boscan', admin: true  },
        { name: 'Noemi Iturralde', admin: false  },
    ];
  public added: string[] = [];
  x1 = 0; y1 = 0; x2 = 0; y2 = 0;

  @ViewChild('selector') selector: ElementRef;

  constructor(private renderer: Renderer2) {
  }

  isRectangeVisible = false;
  isMouseDown = false;

  @HostListener('mousedown', ['$event'])
  onMouseDown(ev) {
    this.dragStart = ev.clientY;
    this.isMouseDown = true;

  }

  @HostListener('document:mouseup', ['$event'])
  onMouseUp(ev) {
    this.dragStart = 0;
    this.dragOver = 0;
    this.renderer.setStyle(this.selector.nativeElement, 'display', 'none');
    this.isRectangeVisible = false;
    this.isMouseDown = false;
  }

  @HostListener('document:mousemove', ['$event'])
  onMouseMove(ev) {

    if(!this.isRectangeVisible && this.isMouseDown){
      this.renderer.setStyle(this.selector.nativeElement, 'display', 'block');
      this.x1 = ev.clientX;
      this.y1 = ev.clientY;
      this.isRectangeVisible = true;
    }

    this.x2 = ev.clientX;
    this.y2 = ev.clientY;
    this.reCalc();
  }

  reCalc() {
    const x3 = Math.min(this.x1, this.x2);
    const x4 = Math.max(this.x1, this.x2);
    const y3 = Math.min(this.y1, this.y2);
    const y4 = Math.max(this.y1, this.y2);
    this.renderer.setStyle(this.selector.nativeElement, 'left', x3 + 'px');
    this.renderer.setStyle(this.selector.nativeElement, 'top', y3 + 'px');
    this.renderer.setStyle(this.selector.nativeElement, 'width', x4 - x3 + 'px');
    this.renderer.setStyle(this.selector.nativeElement, 'height', y4 - y3 + 'px');
  }

  onSelecUser(item) {
        if(this.added.indexOf(item.name)===-1) { // or compare by id
            this.added = this.added.concat([item.name]);
        }
        else {
            this.added = this.added.filter((x) => item.name!==x); // or compare by id
        }

        item.selected = !item.selected ? true : false;
    }

    onMouseOver(ev, item) {
        if(ev.which!==1) {
            return false;
        }

        ev.preventDefault();

        if(ev.type==='mouseenter' && !item.selected) {
            this.dragOver = ev.clientY - this.dragStart > 0 ? 1:-1;
            this.onSelecUser(item);
            return false;
        }

        if(ev.type==='mouseleave') {
            if(this.dragOver===1 && ev.clientY < ev.target.offsetTop && item.selected) {
                console.log('desel...', item);
                this.onSelecUser(item);
                return false;
            }
            if(this.dragOver===-1 && ev.clientY > ev.target.offsetTop && item.selected) {
                console.log('desel...', item);
                this.onSelecUser(item);
                return false;
            }
        }
    }
}

Thanks for read.

Upvotes: 1

Views: 437

Answers (1)

Timour
Timour

Reputation: 2081

UPDATE #1: https://plnkr.co/edit/d9aTb0E0OKFfTSIAM0MY?p=preview

Added option to select/unselect a user by a click.
For that, no reset code needed.

  @HostListener('mousedown', ['$event'])
  onMouseDown(ev) {
    this.dragStart = ev.clientY;
    this.isMouseDown = true;
  }

Only div's click handler changed.

(click)="onSelecPersona(user, !user.selected)"

INITIAL ANSWER: Here is modified code: https://plnkr.co/edit/QryFWtLQwNuGkrtzDehm?p=preview

It solves few issues:

(1) HTML selection: the "user-select" CSS should be on the "row" no on the "card" because the selection starts at the "row" boundaries

.row {
  user-select: none;
  -moz-user-select: none;
}
.card-content {
  padding: 0;
}

(2) Handling of selected divs: initial implementation relies on mouse events on the user's div. That does not handle the case when "selector" rectangle never crosses "user" div's boundaries (i.e. goes around but still within selection boundaries). My implementation calculates an overlap of a "selector" and "user" divs to determine if a user selected.

     <div class="card"
            #ucard   
            [attr.id]="user.name"
            [class.selected]="user.selected" 
            *ngFor="let user of users" 
            (click)="onSelecPersona(user, !user.selected)"
        >

    import {Component, NgModule, HostListener, Renderer2, ElementRef, ViewChild, ViewChildren } from '@angular/core'
    ...
     @ViewChildren('ucard') components: QueryList<ElementRef>;
    ...
      // return true if two HTML elements overlap 
      overlap(e1:ElementRef, e2:ElementRef){
        var rect1 = e1.getBoundingClientRect();
        var rect2 = e2.getBoundingClientRect();

        return !(
          rect1.top > rect2.bottom ||
          rect1.right < rect2.left ||
          rect1.bottom < rect2.top ||
          rect1.left > rect2.right
        ); 
      }

      // updates user selection based on the current "selector" 
      markSelected(){
        this.components.forEach(it=> {
          var overlaps: boolean = this.overlap(this.selector.nativeElement, it.nativeElement);
          this.onSelecPersona(this.users.find(u=> u.name == it.nativeElement.id), overlaps);
        });
      }

Upvotes: 2

Related Questions