All we need is an easy explanation of the problem, so here it is.
I have an AngularJS component that should react to either a single click or a drag (resizing an area).
I started to use RxJS (ReactiveX) in my application, so I try to find a solution using it. The Angular side of the request is minor…
To simplify the problem (and to train myself), I made a slider directive, based on the rx.angular.js drag’n’drop example: http://plnkr.co/edit/UqdyB2
See the Slide.js file (the other code is for other experiments). The code of this logic is:
function(scope, element, attributes)
{
var thumb = element.children(0);
var sliderPosition = element[0].getBoundingClientRect().left;
var sliderWidth = element[0].getBoundingClientRect().width;
var thumbPosition = thumb[0].getBoundingClientRect().left;
var thumbWidth = thumb[0].getBoundingClientRect().width;
// Based on drag'n'drop example of rx-angular.js
// Get the three major events
var mousedown = rx.Observable.fromEvent(thumb, 'mousedown');
var mousemove = rx.Observable.fromEvent(element, 'mousemove');
var mouseup = rx.Observable.fromEvent($document, 'mouseup');
// I would like to be able to detect a single click vs. click and drag.
// I would say if we get mouseup shortly after mousedown, it is a single click;
// mousedown.delay(200).takeUntil(mouseup)
// .subscribe(function() { console.log('Simple click'); }, undefined, function() { console.log('Simple click completed'); });
var locatedMouseDown = mousedown.map(function (event)
{
event.preventDefault();
// console.log('Click', event.clientX - sliderPosition);
// calculate offsets when mouse down
var initialThumbPosition = thumb[0].getBoundingClientRect().left - sliderPosition;
return { from: initialThumbPosition, offset: event.clientX - sliderPosition };
});
// Combine mouse down with mouse move until mouse up
var mousedrag = locatedMouseDown.flatMap(function (clickInfo)
{
return mousemove.map(function (event)
{
var move = event.clientX - sliderPosition - clickInfo.offset;
// console.log('Move', clickInfo);
// calculate offsets from mouse down to mouse moves
return clickInfo.from + move;
}).takeUntil(mouseup);
});
mousedrag
.map(function (position)
{
if (position < 0)
return 0;
if (position > sliderWidth - thumbWidth)
return sliderWidth - thumbWidth;
return position;
})
.subscribe(function (position)
{
// console.log('Drag', position);
// Update position
thumb.css({ left: position + 'px' });
});
}
That’s mostly D’n’D constrained horizontally and to a given range.
Now, I would like to listen to mousedown, and if mouse up happens within a short while (say 200 ms, to adjust), I see it as a click and I do a specific treatment (eg. resetting the position to zero).
I tried with delay().takeUntil(mouseup)
, as seen in another SO answer, without success. Perhaps a switch() might be needed, too (to avoid going the drag route).
Any idea? Thanks in advance.
How to solve :
I know you bored from this bug, So we are here to help you! Take a deep breath and look at the explanation of your problem. We have many solutions to this problem, But we recommend you to use the first method because it is tested & true method that will 100% work for you.
Method 1
You can use timeout
(timeoutWith
if you are using ReactiveX/RxJS)
var click$ = mousedown.flatMap(function (md) {
return mouseup.timeoutWith(200, Observable.empty());
});
If the mouseup doesn’t occur before the timeout it will just propagate an empty Observable
instead. If it does then the downstream observer will receive an event.
Method 2
Isn’t the trick with delay(Xms).takeUntil(mouseup)
doing the opposite of what you want? I mean, you want to detect when the mouseup
event happens before the countdown, while the aformentioned trick detect when the mouseup event happens after.
I would try something around those lines (untested for now, but hopefully it will orient you in some positive direction):
var click$ = mousedown.flatMap(function ( mouseDownEv ) {
return merge(
Rx.just(mouseDownEv).delay(Xms).map(function ( x ) {return {event : 'noclick'};}),
mouseup.map(function ( mouseUpEv ) {return {event : mouseUpEv};})
).first();
});
The idea is to race the mouseup
event against a dummy emission happening after your delay, and see who wins. So if click$
emits ‘noclick’ then you can consider that no click happened.
Hopefully that works, i will test soon but if you do before me, let me know.
Note: Use and implement method 1 because this method fully tested our system.
Thank you 🙂
All methods was sourced from stackoverflow.com or stackexchange.com, is licensed under cc by-sa 2.5, cc by-sa 3.0 and cc by-sa 4.0