Select Many with Rx and RxJs by ThinqLinq

Select Many with Rx and RxJs

A while back, I showed how you could use the query syntax and Rx to code a drag-drop operation similar to the way you might describe  the process to your mother. As a review, let’s take another look at the code, this time in C# as a preparation for moving it over to javaScript.


var mouseDown = from evt in Observable.FromEventPattern<MouseButtonEventArgs>(image, "MouseLeftButtonDown")
                select evt.EventArgs.GetPosition(image);
var mouseUp = Observable.FromEventPattern&;lt;MouseButtonEventArgs>(this, "MouseLeftButtonUp");
var mouseMove = from evt in Observable.FromEventPattern<MouseEventArgs>(this, "MouseMove")
                select evt.EventArgs.GetPosition(this);

var q = from startLocation in mouseDown
        from endLocation in mouseMove.TakeUntil(mouseUp)
        select new
        {
            X = endLocation.X - startLocation.X,
            Y = endLocation.Y - startLocation.Y
        };

What might not be evident from this code is that the query syntax is actually using the SelectMany extension method when we use the from x in xs from y in ys above. If we wanted to, we could re-write this using the Lambda/method syntax as follows:


var q = mouseDown
        .SelectMany(startPos =>
              mouseMove
                    .TakeUntil(mouseUp)
                    .Select(movePos =>
                        new
                        {
                            X = movePos.X - startPos.X,
                            Y = movePos.Y - startPos.Y
                        })
                     );

Personally, I prefer the query syntax and find the lambda syntax a bit messy with SelectMany. Let’s try to tear this down a bit to see if we can understand what’s going on. Here we still have 3 different sets of Observables—mouseDown, mouseMove and mouseUp. The SelectMany extends mouseDown taking in the instance of each observed value as it is produced. SelectMany then takes a function (lambda) which will generate a new set of Observables of some other type which may or may not include values from the first observed value and values from the second variable.

In this case, we create the second observable set from the mouseMoves that we start listening to as the result of the SelectMany. We can then generate an new observable set by projecting (Select) the mouseDown’s startPos and mouseMove’s movePos offsets. For those who think pictures say more than words, here’s a marble diagram illustrating how SelectMany works:

image

In this case the Ox represents the MouseDown observables. fx represents the function supplied in the Lambda parameter and Oy represents the resulting Observable set of offsets. Wes and Jeffrey walk through SelectMany in their Channel 9 video as well.

Moving to RxJs

So what does all of this have to do with JavaScript? In the process of rewriting some of my Rx talks to RxJs, I wanted to include the MouseMove sample. Since JavaScript doesn’t support the syntactic sugar that enable query expressions, we are forced to use the method syntax. If you want to skip to the chase, you can download my RxJs samples and look at the DragDrop.htm to try this out. As we did with the Silverlight/WPF version of DragDrop, we start by setting up three sets of Observables to track the MouseDown, MouseMove and MouseUp events.


    <script type="text/javascript" src="Scripts/rx.js"></script>
    <script type="text/javascript" src="Scripts/jquery-1.4.1.min.js"></script>
    <script type="text/javascript" src="Scripts/rx.jQuery.js"></script>

    <script type="text/javascript">
        $(document).ready(function () {
            var dragTarget = $("img");
            var mouseUp = dragTarget.toObservable("mouseup");
            var mouseMove = dragTarget.toObservable("mousemove");
            var mouseDown = dragTarget.toObservable("mousedown")
                .Select(function (event) {
                    return {
                        left: event.clientX - dragTarget.offset().left,
                        top: event.clientY - dragTarget.offset().top
                    };
                });
    </script>
    <img alt="logo" src="Images/RxLogo.png" style="position: absolute; top:400; left:400; height: 100;
        width: 100" id="RxLogo" />

Now that we have our observables, we can merge the streams using SelectMany and subscribe to the end result to actually move the image on the screen by altering the css left and top positions accordingly.


            var moves = mouseDown.SelectMany(function (imageOffset) {
                return mouseMove
                        .Do(function (event) { event.preventDefault(); })
                        .Select(function (pos) {
                            return {
                               left: pos.clientX - imageOffset.left,
                                top: pos.clientY - imageOffset.top
                            };
                        })
                        .TakeUntil(mouseUp);
            });
            moves.Subscribe(function (pos) {
                dragTarget.css("left", pos.left);
                dragTarget.css("top", pos.top);
            });

With the exception of the JavaScript function syntax which I tend to think of as a hybrid of C# and VB’s lambda syntaxes, we essentially have the same code that we did with the C# lambda syntax for SelectMany.

It can take a bit of mind twisting to get your head around the lambda syntax for SelectMany, but once you’ve done that, you can start doing some powerful manipulations to extend and coordinate observable event streams in both .Net and RxJs.

Posted on - Comment
Categories: C# - JQuery - Rx -
comments powered by Disqus