Introduction to the Reactive Extensions to JavaScript
Readers of my blog probably know that I’m a bit into functional programming languages (F#, Erlang, Clojure, Haskell, etc) among other topics, but what you may not know is that I’m a huge JavaScript fan as well. Since I began in the industry (professionally anyways), I’ve been using JavaScript to knock out some pretty interesting solutions. Over the years, many people have tried to abstract over the language, taking such approaches as taking a statically typed language and compiling it to JavaScript, but when it comes down to it, I prefer dealing with the natural language of the web, which is HTML, CSS and native JavaScript.
As much as I love the JavaScript language, it has its set of warts primarily from DOM manipulation.
The Problem with JavaScript Eventing
One of the issues around DOM manipulation has been around DOM Events. The inconsistencies between the browsers were apparent, but just as well, how could you make events composable much as your would say functions? For example, how could I do a simple mouse drag over a div which gathers not only the current value but also the previous? We’d need some sort of global state, e.g. the previous mouse location. Below is a quick example using plain old JavaScript without the aid of any libraries like jQuery, etc.
<head> <!-- style omitted --> <script type="text/javascript"> var oldMouse = null; function tester() { var mouseDragMe = document.getElementById("mouseDragMe"); mouseDragMe.onmousemove = handleMouseMove; mouseDragMe.onmousedown = function(ev) { ev = ev || window.event; oldMouse = { x : ev.clientX, y : ev.clientY }; } mouseDragMe.onmouseup = function(ev) { oldMouse = null; } } function handleMouseMove(ev){ ev = ev || window.event; var results = document.getElementById("results"); if(oldMouse) { results.innerHTML = "Old (X: " + oldMouse.x + " Y: " + oldMouse.y + ") " + "New (X: " + ev.clientX + " Y: " + ev.clientY + ")"; oldMouse = { x : ev.clientX, y : ev.clientY }; } } </script> </head> <body onload="tester()"> <div id="mouseDragMe"></div> <div id="results"/> </body>
This is a rather simplistic example which adds handlers to DOM events to set the old mouse position when the mouse is down and wipes it out when the mouse goes up. Then while the mouse is moving, we check if the old mouse position has been set and if it has, print out the value, and finally set the old value to our new one. If we start adding in additional requirements such as throttling, sampling and so forth, the code gets a bit more complicated. So, the code as is works, but is a bit messy and not really composable. And what I mean by composable is to say I want to chain events together to say do this and that until something else happens and make it look like this:
someEvent.mergeWith(someOtherEvent).Until(yetAnotherEvent);
Other Abstractions
Libraries have come along over time to help us deal with the pain that is DOM manipulation. One in particular that is worth mentioning would be jQuery, which provides us some better flexibility when dealing with events. For example, I can take the above code and rewrite it to use jQuery and in particular the bind function which allows me to wire up all of my events through the use of a map approach.
<script src="http://code.jquery.com/jquery-latest.js"></script> <script type="text/javascript"> var old = null; $(document).ready(function() { $("#mouseDragMe").bind({ mouseup: function(ev) { old = null; }, mousedown: function(ev) { old = { x : ev.clientX, y : ev.clientY }; }, mousemove: function(ev) { if(old) { $("#results").html( "Old (X: " + old.x + " Y: " + old.y + ") " + "New (X: " + ev.clientX + " Y: " + ev.clientY + ")"); old = { x : ev.clientX, y : ev.clientY }; } } }); }); </script>
This does quite a bit to alleviate some of the earlier pains I had with eventing to not have to do the extra work to use the Event class, but also be able to wire up multiple events to the same handler if I so choose. But that doesn’t get me any closer to dealing with events as first class citizens and make them composable?
Introducing the 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. I’ve covered this in a few posts and will resume coverage shortly on some of the more interesting bits. 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.
To get started, let’s create observers on the three events we care about on our mouseDragMe div tag, mousemove, mouseup and mousedown. To wire up our events, we get our DOM object, in the case the mouseDragMe object, and then call Rx.Observable.FromHtmlEvent with our object and a string containing the name of the event, minus the “on” prefix. Instead of using the stanard document.getElementById, I thought I’d use a little jQuery as well to provide the context as well.
<script src="http://code.jquery.com/jquery-latest.js"></script> <script src="rx.js" type="text/javascript"></script> <script type="text/javascript"> $(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"); }); </script>
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.
Conclusion
This of course is only scratching the surface of what capabilities this library has and there is much more yet left to cover. The question you’re probably asking now is where can I get it? Well, for that you’ll have to stay tuned. I hope to have more announcements soon about its general availability.
What can I say? I love JavaScript and very much looking forward to the upcoming JSConf 2010 here in Washington, DC. For too many times, we’ve looked for the abstractions over the natural language of the web (HTML, CSS and JavaScript) and created monstrosities instead of embracing the web for what it is. With libraries such as jQuery and indeed the Reactive Extensions for JavaScript gives us better tools for dealing with the troubled child that is DOM manipulation and especially events.