Thursday, April 22, 2010

Reactive Extensions for Native JavaScript

As you may recall, Erik Meijer and his team on the Cloud Programmability Group in Building 35 have been working on the Reactive Extensions for .NET which is available on both the .NET CLR as well as Silverlight (posts). While working on the Reactive Extensions for .NET, the team also ventured into creating the same kind of functionality for native JavaScript.  That means we get to use HTML and DOM events as if they were first class members instead of relegated to simple assignments.  Most of the methods that are available for the .NET version are also available for the JavaScript version.
$(document).ready(function() {
    var mouseDragMe = $("#mouseDragMe").context
    
    var mouseMove = Rx.Observable.FromHtmlEvent(mouseDragMe, "mousemove");
    var mouseUp = Rx.Observable.FromHtmlEvent(mouseDragMe, "mouseup");
    var mouseDown = Rx.Observable.FromHtmlEvent(mouseDragMe, "mousedown");
});

Now that we have these first class events, we can use any of the standard LINQ methods such as Select, Where, Scan, Zip and so on.  We’ll cover all of those in the posts going forward.  But for now, what we want to do is provide the same mouse delta dragger as we’ve had above, but this time using no “global state” and instead use composability to express our intent.  With that, let’s take our mousemove and zip it together with our mouse move that skipped once so that we have an offset between our previous and our current value.  Then we can take the first and second sets of our mouse events and combine them into a single object.
var mouseMoves = mouseMove
    .Skip(1)
    .Zip(mouseMove, function(left, right) {
        return { x1 : left.clientX,
                 y1 : left.clientY,
                 x2 : right.clientX,
                 y2 : right.clientY };
    });
As you can see, we call the Skip method with a parameter of 1 which allows us to skip one instance of the mousemove firing, thus giving our offset.  Then, we zip the two instances of the mousemove together to create an object which has the previous and the current mouse points.  Now, what we need is a way to only fire these mouse events when the mouse button is down.  Let’s look at the code required to do that.
var mouseDrags = mouseDown.SelectMany(function(md) {
    return mouseMoves.TakeUntil(mouseUp);
});
What we did was take our mousedown observable and call the SelectMany which projects each value of an observable sequence (in this case our mousedown) to an observable sequence and flattens the resulting observable sequences into one observable sequence.  Inside our SelectMany, we return our mouseMoves observable from above and we call TakeUntil passing in our mouseUp observable.  Finally, much like in the Reactive Extensions for .NET, we can call subscribe to our resulting observable which allows us to set the inner HTML of our resulting div.
mouseDrags.Subscribe(function(mouseEvents) {
    $("#results").html(
        "Old (X: " + mouseEvents.x1 + " Y: " + mouseEvents.y1 + ") " +
        "New (X: " + mouseEvents.x2 + " Y: " + mouseEvents.y2 + ")");
});
And there you have it, a full dragging capability using composable events in native JavaScript.

http://codebetter.com/blogs/matthew.podwysocki/archive/2010/02/16/introduction-to-the-reactive-extensions-to-javascript.aspx

No comments: