5

Started using RxJs. Can't find a way around this problem. I have a draggable control:

startDrag = rx.Observable.fromEvent(myElem,'mousedown')

now, because the control is too small mousemove and mouseup events should be at document level (otherwise it won't stop dragging unless cursor exactly on the element)

endDrag = rx.Observable.fromEvent document,'mouseup'

position = startDrag.flatMap ->
   rx.Observable.fromEvent document,'mousemove'
   .map (x)-> x.clientX
   .takeUntil endDrag

now how do I "catch" the right moment when is not being dragged anymore (mouseup). you see the problem with subscribing to endDrag? It will fire every time clicked anywhere, and not just myElem How do I check all 3 properties at once? It should take only those document.mouseups that happened exactly after startDrag and position

Upd: I mean the problem is not with moving the element. That part is easy - subscribe to position, change element's css. My problem is - I need to detect the moment of mouseup and know the exact element that's been dragged (there are multiple elements on the page). How to do that I have no idea.

iLemming
  • 34,477
  • 60
  • 195
  • 309

1 Answers1

8

I have adapted the drag and drop example provided at the RxJS repo to behave as you need.

Notable changes:

  • mouseUp listens at document.

  • The targeted element is added to the return from select.

  • Drag movements are handled inside of map and map returns the element that was targeted in the mouseDown event.

  • Call last after takeUntil(mouseUp) so subscribe will only be reached when the drag process ends (once per drag).

Working example:

function main() {
  var dragTarget = document.getElementById('dragTarget');

  // Get the three major events
  var mouseup = Rx.Observable.fromEvent(document, 'mouseup');
  var mousemove = Rx.Observable.fromEvent(document, 'mousemove');
  var mousedown = Rx.Observable.fromEvent(dragTarget, 'mousedown');


  var mousedrag = mousedown.selectMany(function(md) {

    // calculate offsets when mouse down
    var startX = md.offsetX;
    var startY = md.offsetY;

    // Calculate delta with mousemove until mouseup
    return mousemove.select(function(mm) {
      if (mm.preventDefault) mm.preventDefault();
      else event.returnValue = false;

      return {
        // Include the targeted element
        elem: mm.target,
        pos: {
          left: mm.clientX - startX,
          top: mm.clientY - startY
        }
      };
    })
    .map(function(data) {
      // Update position
      dragTarget.style.top = data.pos.top + 'px';
      dragTarget.style.left = data.pos.left + 'px';

      // Just return the element
      return data.elem;
    })
    .takeUntil(mouseup)
    .last();
  });


  // Here we receive the element when the drag is finished
  subscription = mousedrag.subscribe(function(elem) {
    alert('Drag ended on #' + elem.id);
  });
}
main();
#dragTarget {
  position: absolute;
  width: 20px;
  height: 20px;
  background: #0f0;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/4.0.7/rx.all.min.js"></script>
<div id="dragTarget"></div>
Jon Surrell
  • 9,444
  • 8
  • 48
  • 54
  • 1
    Wow... thank you Jon! This seems to be working for me incredibly well. Heck. Rx could be really confusing for starters... – iLemming Feb 12 '15 at 19:21
  • yeah the key is that you put the `takeUntil()` *inside* the `flatMap` instead of after, so that it is applied to individual drags instead of the overall observable – Brandon Feb 12 '15 at 19:37
  • @Agzam glad you found it helpful. I enjoy digging into these sorts of problems :) – Jon Surrell Feb 12 '15 at 19:51
  • @VishalSakaria yeah! You can do some really cool stuff with Rx. – Jon Surrell Oct 19 '16 at 12:15