Question

I'm trying to achieve the same effect I'm posting here using Reactive Extensions for Javascript (RX-JS). I'm a bit puzzled on how to do it. Here is the page:

      <!DOCTYPE html>
      <html>
      <head>
        <title>drag and drop</title>
      </head>
      <style type="text/css">

      canvas {
        border:1px solid steelblue;
        background-color: whitesmoke;
      }

      </style>
      <body>
        <canvas id="canvas" width=300 height=300></canvas>
        <script type="text/javascript" src="http://code.jquery.com/jquery-1.11.0.min.js"></script>
        <script type="text/javascript">
        $(function() {
            var canvas = document.getElementById("canvas");
            var ctx = canvas.getContext("2d");

            var canvasOffset = $("#canvas").offset();
            var offsetX = canvasOffset.left;
            var offsetY = canvasOffset.top;

            var drawing = false;
            var mouseX = 0;
            var mouseY = 0;


            function handleMouseDown(e) {
                mouseX = parseInt(e.clientX - offsetX);
                mouseY = parseInt(e.clientY - offsetY);         
                drawing= true;                
        }

        function handleMouseUp(e) {             
            drawing = false;    
        }
        function handleMouseMove(e) {
            if(drawing){
              mouseeX = parseInt(e.clientX - offsetX);
              mouseeY = parseInt(e.clientY - offsetY);
              $("#movelog").html("Move: " + mouseX + " / " + mouseY);

                var ctx =  canvas.getContext("2d");
                // some cleanup code
                ctx.save();
                ctx.setTransform(1, 0, 0, 1, 0, 0);
                ctx.clearRect(0, 0, canvas.width, canvas.height);
                ctx.restore();          
                ctx.beginPath();
                ctx.moveTo(mouseX,mouseY);
                ctx.lineTo(mouseeX,mouseeY);
                ctx.stroke();
             }
        }

        $("#canvas").mousedown(function(e) {
            handleMouseDown(e);
        });
        $("#canvas").mousemove(function(e) {
            handleMouseMove(e);
        });
        $("#canvas").mouseup(function(e) {
            handleMouseUp(e);
        });

      });
      </script> 
      </body>
      </html>

I think I should create observables for the mouseDown, mouseMove and mouseUp events.

    var mouseDown = Rx.Observable.fromEvent(canvas, 'mousedown');
    var mouseMove = Rx.Observable.fromEvent(canvas, 'mousemove');
    var mouseUp = Rx.Observable.fromEvent(canvas, 'mouseup');

but I don't know how to combine them. I think should start observing the mousedown, then collect all the moves until the mouseup is raised, and in the whil redraw the line from the starting point to the current point where the mouse is during the mousemove. Do you have any ideas? Thanks a lot.

°°°°°°°°°°°°°°°°°°°°°°EDIT°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°

Here is my code after the answer by Brandon:

              $(function() {

                  var canvas = document.getElementById('canvas');
                  var ctx = canvas.getContext("2d");
                  var canvasOffset = $("#canvas").offset();
                  var offsetX = canvasOffset.left;
                  var offsetY = canvasOffset.top;

                  var mouseDown = Rx.Observable.fromEvent($("#canvas"), 'mousedown');
                  var mouseMove = Rx.Observable.fromEvent($("#canvas"), 'mousemove');
                  var mouseUp = Rx.Observable.fromEvent($("#canvas"), 'mouseup');

                  // keep a reference to the pisition when the mouse down was fired
                  // then flatten the stream with concatAll


                  var traceLineStream = mouseDown.map(function(md) {
                      var movesFromMouseDown = mouseMove.takeUntil(mouseUp);
                      var movesFromMouseDownAndMouseDown = movesFromMouseDown.map(function(mm) {
                          return {
                              mouseDownPoint: md,
                              mouseMovePoint: mm
                          }
                      });
                      return movesFromMouseDownAndMouseDown;
                  }).concatAll();


                  var subscription = traceLineStream.subscribe(
                      function(y) {

                          var mouseDown = y.mouseDownPoint;
                          var mouseMove = y.mouseMovePoint;

                          var mouseDownX = parseInt(mouseDown.clientX - offsetX);
                          var mouseDownY = parseInt(mouseDown.clientY - offsetY);               

                          var mouseMoveX = parseInt(mouseMove.clientX - offsetX);
                          var mouseMoveY = parseInt(mouseMove.clientY - offsetY);

                          ctx.save();
                          ctx.setTransform(1, 0, 0, 1, 0, 0);
                          ctx.clearRect(0, 0, canvas.width, canvas.height);
                          ctx.restore();
                          ctx.beginPath();
                          ctx.moveTo(mouseDownX, mouseDownY);
                          ctx.lineTo(mouseMoveX, mouseMoveY);
                          ctx.stroke();

                      },
                      function(e) {
                          console.log('onError: ' + e.message);
                      },
                      function() {
                          console.log('onCompleted');
                      });
              });
Was it helpful?

Solution

First, combined the streams so you have a stream of events that represent a single drag.

var drag = mouseDown.first().concat(mouseMove.takeUntil(mouseUp));

Next, project this stream of events into a stream of previous,current tuples.

var moves = drag
    .scan({}, function(acc, x) {
        return { previous: acc.current, current: x };
    })
    .skip(1);

Now we have a stream that only works the first time. When it ends, we want to start listening for the next drag:

var allMoves = moves.repeat();

Finally, subscribe:

allMoves.subscribe(function (move) {
    var mouseX = move.previous.clientX - offsetX,
        mouseY = move.previous.clientY - offsetY,
        mouseeX = move.current.clientX - offsetX,
        mouseeY = move.current.clientY - offsetY,
    ...
});

Putting it all together without all of the intermediate variables:

mouseDown
    .first()
    .concat(mouseMove.takeUntil(mouseUp))
    .scan({}, function(acc, x) {
        return { previous: acc.current, current: x };
    })
    .skip(1)
    .repeat()
    .subscribe(function (move) {
        var mouseX = move.previous.clientX - offsetX,
            mouseY = move.previous.clientY - offsetY,
            mouseeX = move.current.clientX - offsetX,
            mouseeY = move.current.clientY - offsetY,
        ...
    });
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top