0
votes

I am trying to create my own click, hold and drag events using Rxjs and the mousedown, mouseup and mousemove events. My attempts use a number of streams that begin with a mousedown event, each with a takeUntil that listens for emissions from the other streams. Basically once one of the streams has "claimed" the action (i.e. passed all the requirements and emitted a value) the other observables should complete with no emissions.

I have looked at other answers and thought it might have something to do with the timer running async but it happens between streams that do not rely the timer e.g. drag and click. I have been playing around in codesandbox.io using rxjs v6.

The takeUntil's also have to sit on the inner observables as I don't want the outer observables to run once and complete.

The code is shown below:

const mouse_Down$ = fromEvent(document, "mousedown").pipe(
  tap(event => event.preventDefault())
);

const mouse_Up$ = fromEvent(document, "mouseup").pipe(
  tap(event => event.preventDefault())
);

const mouse_Move$ = fromEvent(document, "mousemove");

const mouse_drag$ = mouse_Down$
  .pipe(
    mergeMap(mouseDownEvent =>
      mouse_Move$.pipe(takeUntil(merge(mouse_Up$, mouse_Hold$, mouse_drag$)))
    )
  ).subscribe(event => console.log("Drag"));

const mouse_Hold$ = mouse_Down$
  .pipe(
    mergeMap(mouseDownEvent =>
      timer(1000).pipe(takeUntil(merge(mouse_drag$, mouse_Click$)))
    )
  ).subscribe(event => console.log("Hold"));

const mouse_Click$ = mouse_Down$
  .pipe(
    mergeMap(mouseDownEvent =>
      mouse_Up$.pipe(takeUntil(mouse_drag$, mouse_Hold$))
    )
  ).subscribe(event => console.log("Click"));

Expected behaviour: If the user moves the mouse within 1s of the mousedown event the mouse_drag$ stream should begin emitting and the mouse_Click$/mouse_Hold$'s inner observables should complete (thanks to the takeUntil(mouse_drag$) without emitting and await the next mouse_down$ emmission.

If the mouse button remains down for more than 1s without moving the mouse_Hold$ should emit and mouse_drag$/mouse_click$'s inner observable should complete (thanks to the takeUntil(mouse_Hold$) without emitting and await the next mouse_down$ emmission.

Actual Behaviour: Currently the mouse_Drag$ will emit, the mouse_Hold$ will emit after one second and the mouse_Click$ will emit when the button is released.

My question is why doesn't the emitting mouse_Drag$ stream cause the mouse_Hold$ and mouse_Click$'s inner observable to complete without emitting?

2

2 Answers

0
votes

The take until needs to be at the end of your chain

This will cancel the whole chain.

const { fromEvent } = rxjs;
const { tap, takeUntil, mergeMap, merge } = rxjs.operators;

const mouse_Down$ = fromEvent(document, "mousedown").pipe(
  tap(event => event.preventDefault())
);

const mouse_Up$ = fromEvent(document, "mouseup").pipe(
  tap(event => event.preventDefault())
);

const mouse_Move$ = fromEvent(document, "mousemove");

const mouse_drag$ = mouse_Down$
  .pipe(
    mergeMap(mouseDownEvent =>
      mouse_Move$
    ),
    takeUntil(mouse_Up$)
  ).subscribe(event => console.log("Drag"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/6.4.0/rxjs.umd.min.js"></script>
0
votes

To clarify:

  1. that you want emit from mouse_Hold$ if mouse is hold more then 1 second.
  2. You want to get values from mouse_drag$ if LESS then 1 second pass after mouse dropdown and mouseMove.

You do not have to complete anything since otherwise all behaviour will work only once. So plan: 3. mouse_drag$ - If mousedown - check mouseMove for 1 second. If mouseMove emits - switch to mouseMove values 4. mouse_Hold$ - if mouseDown - check mouseMove for 1 second. If mouseMove doesn't emit - switch to mouseHold and make it emit 'Hold'

let Rx = window['rxjs'];
const {defer, of, timer, fromEvent, merge, race} = Rx;
const {switchMap, repeat, tap, takeUntil, filter} = Rx.operators;
const {ajax} = Rx.ajax;
console.clear();

const mouse_Down$ = fromEvent(document, "mousedown");

const mouse_Up$ = fromEvent(document, "mouseup");

const mouse_Move$ = fromEvent(document, "mousemove");

const timer$ = timer(2000);

mouse_Hold$ = mouse_Down$.pipe(
  switchMap((downEvent) => {
    return timer$.pipe(
      switchMap((time) => of('HOLD'))
    );
  }),
  takeUntil(merge(mouse_Up$, mouse_Move$)),
  repeat(mouse_Down$)
)

mouse_Hold$.subscribe(console.warn);

mouse_drags$ = mouse_Down$.pipe(
  switchMap(() => mouse_Move$),
    takeUntil(mouse_Up$, $mouse_Hold),
  repeat(mouse_Down$)
)

mouse_drags$.subscribe(console.log);

Here is a codepen: https://codepen.io/kievsash/pen/oOmMwp?editors=0010