2
votes

I am trying to capture hover events on a list of items between the first and second clicks of any of items in the list.

E.g.

  • User clicks item in list
  • Hover stream items emits event
  • User performs second click in list and hover events stop

I have setup an observable stream as follows:

let items = document.getElementsByClassName("item");
let itemClicks = Rx.Observable.fromEvent(items, "click");
let itemHover = Rx.Observable.fromEvent(items, "mouseenter");

let clicksWithFlag = itemClicks.scan((acc, val) => ({ val: val.target.innerHTML, firstClick: !acc.firstClick}), {});

//create stream of 1st clicks
let firstClicks = clicksWithFlag.filter(x => x.firstClick).pluck('val');
//create stream of second clicks
let secondClicks = clicksWithFlag.filter(x => !x.firstClick).pluck('val');

let hoverBetweenFirstAndSecondClick = firstClicks.flatMap(x => itemHover.takeUntil(secondClicks));

The problem I am running into is the itemHover.takeUntil(secondClicks) doesn't end the itemHover stream. I think it's related to firstClicks and secondClicks stemming from the same observable, but can't figure it out.

It works if I swap the takeUntil(secondClicks) for any other stream (e.g. a stop button).

See this JSBin for an example: https://jsbin.com/dotapis/edit?js,console,output

Hope somebody can help. I am trying to build a range selector using RxJS, but am starting to think that splitting the stream into first and second clicks may not be the best route.

Update

Just tried this with BaconJS and I can get it working with pretty much the exact same code. https://jsbin.com/fobumu/edit?js,console,output

I'm convinced the problem lies in non-idiomatic use of streams, but not sure why.

1

1 Answers

0
votes

It seems you might be trying to force an imperative approach into a functional programming library, that's why it's becoming so complicated to make it work. This might be simpler:

let items = document.getElementsByClassName("item");
let button = document.getElementById("button1");

let itemClicks = Rx.Observable.fromEvent(items, 'click');
let itemHover = Rx.Observable.fromEvent(items, 'mouseenter');

let buttonClicks = Rx.Observable.fromEvent(button, 'click');

let clicksWithFlag = itemClicks.scan((acc, val) => (
  { val: val.target.innerHTML, firstClick: !acc.firstClick}
), {});

clicksWithFlag
  .combineLatest(itemHover, (click, hover) => ({click, hover}))
  .filter(arg => arg.click.firstClick)
  .map(arg => arg.hover.target.innerHTML)
  .subscribe(hover => console.warn('btn: hover: ' + hover));

As a general advise when working with Rx, try to find declarative solutions. If you start thinking how you transform a stream in order to get what you need instead of the steps to make that, things become easier.

Hope it helps, good luck!