bjorkblom
bjorkblom

Reputation: 1959

Drag and drop with RXjs

I'm struggling with a drag and drop behavior with RXJS. I would like to start drag an element after 250ms mouse down for not hijack click events on that element.

So far the start drag works but stop drag never get called. Anyone know why?

let button = document.querySelector('.button');

let mouseDownStream = Rx.Observable.fromEvent(button, 'mousedown');
let mouseUpStream = Rx.Observable.fromEvent(button, 'mouseup');

let dragStream = mouseDownStream
  .flatMap((event) => {
    return Rx.Observable
      .return(event)
      .delay(250)
      .takeUntil(mouseUpStream)
  });

let dropStream = mouseUpStream
  .flatMap((event) => {
    return Rx.Observable
      .return(event)
      .skipUntil(dragStream)
  });

dragStream.subscribe(event => console.log('start drag'));
dropStream.subscribe(event => console.log('stop drag'));

JSBin

Upvotes: 1

Views: 2728

Answers (2)

Ray Luxembourg
Ray Luxembourg

Reputation: 196

Universal drag event with TypeScript and newer rxjs

export const DragEvent = (selector:string):Observable<boolean> => {

  //element where the drag event should be recorded (make sure element is in the dom)
  const elem = document.querySelector(selector);

  // touch events to handle mobile devices
  const touchStart$ = fromEvent<TouchEvent>(elem, 'touchstart');

  const touchEnd$ = fromEvent<TouchEvent>(elem, 'touchend');

  const touchMove$ = fromEvent<TouchEvent>(elem, 'touchmove');

  // mouse events for desktop users
  const mouseDown$ = fromEvent<MouseEvent>(elem, 'mousedown');

  const mouseUp$ = fromEvent<MouseEvent>(elem, 'mouseup');

  const mouseMove$ = fromEvent<MouseEvent>(elem, 'mousemove');

  //Mouse drag event
  const mouseDragging$ = mouseMove$.pipe(
    skipUntil(mouseDown$),
    takeUntil(mouseUp$)
  );

  // 
  const mapToBoolean = (bool) => map(() => bool);

  //universal drag event will emit true on drag (desktop/mobile) optional:add touchStart$ to the merge.
  const move$ = merge(mouseDragging$, touchMove$).pipe(mapToBoolean(true);

  //universal end of drag event will emit false on drag end (desktop/mobile)
  const end$ = merge(mouseUp$, touchEnd$).pipe(mapToBoolean(false);

  //merged to return true or false depending on user dragg
  return merge(move$, end$).pipe(distinctUntilChanged());

}

Upvotes: 0

Olaf Horstmann
Olaf Horstmann

Reputation: 16892

I've updated your code-sample to make it run, what I did:

  • exchanged the flatMaps with switchMaps (switchMap is an alias for flatMapLatest) this will ensure that it only takes the latest events and in case a new event is emitted, it will cancel any old subevent => in this case flatMap might work okay, but it is safer to use switchMap, also a rule of thumb: when in doubt: use switchMap
  • dropStream is based on/initiated by dragStream now
  • removed the skipUntil, which was a racing-condition issue because it would have first triggered after the next dragStream-emission after some mouseUp (which would require traveling back in time)
  • exchanged the mouseUp-target from button to document (more a convenience-thing, and not really essential for the while thing to work)

let button = document.querySelector('.button');

let mouseDownStream = Rx.Observable.fromEvent(button, 'mousedown');
let mouseUpStream = Rx.Observable.fromEvent(document, 'mouseup');

let dragStream = mouseDownStream
  .switchMap((event) => {
    return Rx.Observable
      .return(event)
      .delay(250)
      .takeUntil(mouseUpStream)
  });

let dropStream = dragStream
  .switchMap(() => mouseUpStream.take(1))

dragStream.subscribe(event => console.log('start drag'));
dropStream.subscribe(event => console.log('stop drag'));
<!DOCTYPE html>
<html>
<head>
<script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/4.1.0/rx.all.js"></script>
  <meta charset="utf-8">
  <title>JS Bin</title>
</head>
<body>
<button class="button">Button</button>
</body>
</html>

Upvotes: 5

Related Questions