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.
The basic difference between a click and a drag event is the mouse movement. The mouse event that differentiates a click and drag event is the “mouse move” event. In a “click” event, there is no “mouse move” event. However, the “mouse down” and “mouse up” event remains the same for both click and drag.
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.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With