Reactive Extensions for JavaScript: The Time flies like an arrow sample

As we just released the Reactive Extensions for Javascript I thought it would be nice to dig a bit deeper in what the sample that ships with the download is all about.

For those who haven’t downloaded the bits yet, or haven’t gotten the time to play with the sample, I’ve embedded it right here.

Try moving the mouse over this box

So let's dig into the details of this sample.

  • First we need to include jQuery and the Rx for JS library:

    <script src="https://code.jquery.com/jquery-latest.js" type="text/javascript"></script><script src="rx.js" type="text/javascript"></script>

  • Next we need to get notifications everytime a mousemove event comes in from the document via jQuery. To do this, we ask the Rx library to give us an observable collection that represents a jQuery event. the Rx library will call OnNext on observers subscribed to this observable every mouse move.

    var mouseMove = Rx.Observable.FromJQueryEvent($(document), "mousemove");

  • As we want to move each character over the screen seperately, we need to create ui elements for each character and add them to the DOM:

    var text = "time flies like an arrow";var container = $("#container");for (var i = 0; i < text.length; i++){     var s = $(document.createElement("span")); s.html(text.charAt(i)); s.css({ position: "absolute"}); s.appendTo(container);}

  • Now let's try to hook up the mouse move observable to the individual elements. In Rx this is done by subscribing an Observer to an Observable. An Observer has an OnNext, OnError and OnCompleted message. As often we're only interested in OnNext, we can simply ignore the OnError and OnCompleted functions and Rx will automatically pick defaults for those methods. (OnError will throw and OnCompleted becomes a no-op):

    var text = "time flies like an arrow";var container = $("#container");for (var i = 0; i < text.length; i++){     var s = $(document.createElement("span")); s.html(text.charAt(i)); s.css({ position: "absolute"}); s.appendTo(container);

       mouseMove.Subscribe(function(mouseEvent) { s.css({ top: mouseEvent.offsetY + "px", left: mouseEvent.offsetX + "px" });    });}

  • As you can see only the last character starts moving. The reason for this is the way that javascript closures work. As the subscribe function is executed only when mousemove fires, it is not executed as part of the scope of the for loop. As the value of s gets overridden for each itteration of the for loop, s will point to the last character of the string by the time the mousemove event is triggered.

    We can change this behavior by wrapping the body of the for loop in a function, which will change the scoping of the variables:

    var text = "time flies like an arrow";var container = $("#container");for (var i = 0; i < text.length; i++){     function(i) { var s = $(document.createElement("span")); s.html(text.charAt(i)); s.css({ position: "absolute"}); s.appendTo(container);

           mouseMove.Subscribe(function(mouseEvent) { s.css({ top: mouseEvent.offsetY + "px", left: mouseEvent.offsetX + "px" });        }); })(i);}

  • Let's make a small layout modification to make the letters layout next to each other instead of on top of each other by shifting the left position by a multiple of the position of the character:

    left: mouseEvent.clientX + i * 10 + 15 +

    "px"
  • As all the different characters get the mousemove notification at the same time, the text follows the mouse as a single line. Let's look at how Rx can help us make each letter lag just a bit more behind the mouse cursor. To do this we're going to modify our observable collection. Rx has a whole algebra of operations you can perform on observable collections. The operator we're going to look at is Delay.

    Delay is defined on the Rx.Observable prototype and it takes a dueTime as an argument, it returns a new observable collection. When a notification arrives on the original observable collection, the operator will delay that notification for the time specified by dueTime and after that time will fire out the notification on the resulting observable collection.

    Rx is a fluent api so we can insert the call to Delay directly in one of our expressions dealing with an observable collection:

    mouseMove.Delay(i * 100).Subscribe(

    function(mouseEvent)

    Leading to the final version:

    var text = "time flies like an arrow";var container = $("#container");for (var i = 0; i < text.length; i++){     function(i) { var s = $(document.createElement("span")); s.html(text.charAt(i)); s.css({ position: "absolute"}); s.appendTo(container);

           mouseMove.Delay(i * 100).Subscribe(function(mouseEvent) { s.css({ top: mouseEvent.offsetY + "px", left: mouseEvent.offsetX mouseEvent.clientX + i * 10 + 15 + "px" });        }); })(i);}

That's it, we just finished the Hello World of Rx. We're using conversions from existing events into observable collections, subscribing to the observable and finally using one of the time based operators to modify the observable collection.

You can download Rx for JavaScript on Rx's devlab page. Questions can be asked on the Rx forum. If you're looking for more samples about Rx for JavaScript, I can recommend Matthew Podwysocki's blog.

Also don't forget to follow @jvgogh and #RxJS on twitter