Reputation: 1056
I'm trying to detect if the mousedown
event is held for a period of time before a mouseup
.
I'm using timeout() on an Observable created with fromEvent()
to do so, but the timeout returns both Observables.
Below, subscribing to stream returns the event if mousedown
is triggered within 1 second, but it also returns 1.
var mousedown = Rx.Observable.fromEvent(target, 'mousedown');
var stream = mousedown.timeout(1000, Rx.Observable.return(1));
var sub = stream.subscribe(
function (x) {
console.log('Next: '+x);
},
function (err) {
console.log('Err: '+err);
},
function () {
console.log('Complete');
}
);
However, this works as expected:
var source = Rx.Observable.return(42)
.delay(200)
.timeout(1000, Rx.Observable.return(1));
I'd like this code to work:
var mousedown = Rx.Observable.fromEvent(target, 'mousedown');
var mouseup = Rx.Observable.fromEvent(target, 'mouseup');
var clickhold = mousedown
.flatMap(function (){
return mouseup.timeout(1000, Rx.Observable.return('hold'));
})
.filter(function (x) {
return x === 'hold';
});
clickhold.subscribe(
function (x) {
console.log('Next: '+x);
},
function (err) {
console.log('Err: '+err);
},
function () {
console.log('Complete');
}
);
Upvotes: 3
Views: 13366
Reputation: 18125
You came up with a great solution on your own. Here's what I would change:
timer(...).takeUntil(...).select(...)
) out of flatMap
, so it isn't re-allocated for each mouse down.You've got the rest right. For my usage, I usually retain the original mousedown
event and use that instead of 'hold'
. That requires returnValue
and delay
instead of timer
and select
.
var target,
mousedown,
mouseup;
target = document.querySelector('input');
mousedown = Rx.Observable.fromEvent(target, 'mousedown');
mouseup = Rx.Observable.fromEvent(target, 'mouseup');
var clickhold = mousedown
.flatMap(function (e) {
return Rx.Observable
.return(e)
.delay(1000)
.takeUntil(mouseup);
});
clickhold.subscribe(function (x) {
console.log('onNext: ', x);
});
<script src='https://rawgit.com/Reactive-Extensions/RxJS/v.2.5.3/dist/rx.all.js'></script>
<input type='button' value='Button' />
Or, for a completely different approach...
var Observable = Rx.Observable,
fromEvent = Observable.fromEvent.bind(Observable, target),
holdDelay = Observable.empty().delay(1000);
Observable
.merge(
[
fromEvent('mouseup')
.map(empty),
fromEvent('mousedown')
.map(Observable.returnValue)
.map(holdDelay.concat.bind(holdDelay))
]
)
.switchLatest();
Ok so that's weird. I'm really just giving it as food for though, and to show off that this can be done in a number of different ways.
Upvotes: 1
Reputation: 1056
Instead of using timeout
, I used delay
and takeUntil
:
var target,
mousedown,
mouseup;
target = document.querySelector('input');
mousedown = Rx.Observable.fromEvent(target, 'mousedown');
mouseup = Rx.Observable.fromEvent(target, 'mouseup');
var clickhold = mousedown
.flatMap(function(){
// Triggered instantly after mousedown event.
return Rx.Observable
.return('hold')
.delay(1000)
// Discards the return value if by the time .delay() is complete
// mouseup event has been already completed.
.takeUntil(mouseup);
});
clickhold.subscribe(
function (x) {
console.log('Next: ' + x);
},
function (err) {
console.log('Err: ' + err);
},
function () {
console.log('Complete');
}
);
<script src='https://rawgit.com/Reactive-Extensions/RxJS/v.2.5.3/dist/rx.all.js'></script>
<input type='button' value='Button' />
Upvotes: 6