Introduction to the Reactive Extensions for JavaScript – Drag and Drop
We’ve covered a bit of ground in this series on the Reactive Extensions for JavaScript, covering the basics, jQuery integration, blocking to asynchronous and further composition. One way I’ve found to really get your hands dirty using this library is to take examples from other libraries and translate them into using our library of choice. In this case, I’m taking examples written in the Flapjax language, and translating them into the Reactive Extensions for JavaScript and jQuery.
Before we get started, let’s get caught up to where we are today:
- Introduction
- Creating Observables
- Creating Observers
- jQuery Integration
- Composing Callbacks
- From Blocking to Async
- Wikipedia Lookup
- Composing Deeper
- Bing Maps and Twitter Mashup
Drag and Drop Example
Some of the comments made during my initial introduction to the Reactive Extensions for JavaScript mentioned the Flapjax language and whether I had heard of it. I had indeed, having translated some of the samples from the language to the Reactive Extensions for JavaScript with some assistance with jQuery. Today, we’ll step through a simple drag and drop scenario.
For those unfamiliar with Flapjax, it’s a programming language built on top of JavaScript which deals with event-driven reactive design, much like RxJS. By reading the APIs, you can very much see a Haskell influence in not only the design, but the naming and even down to the notation they use to describe the functions. Not to mention of course that the actual compiler for Flapjax is written in Haskell. Interestingly enough, Erik Meijer hosted an Expert-to-Expert on Channel9 with one of the Flapjax creators, Shriram Krishnamurthi.
Before we move onto the actual drag and drop scenario, let’s look at the most basic example, tracking the mouse movement. In Flapjax, you could write some inline code which gets compiled to native JavaScript such as the following:
<p>The mouse coordinates are < {! mouseLeftB(document) !}, {! mouseTopB(document) !} > </p>
Then, when compiled with Flapjax, will produce real JavaScript like the following which allows us to track the mouse movement:
<p> The mouse coordinates are < <script lang="text/javascript" type=""> function fxcode0() { compilerInsertDomB(mouseLeftB(compilerUnbehavior(document)), "flapjaxsuid0"); } fxcode.push(fxcode0) </script><!-- --><span id="flapjaxsuid0"> </span> , <script lang="text/javascript" type=""> function fxcode1() { compilerInsertDomB(mouseTopB(compilerUnbehavior(document)), "flapjaxsuid1"); } fxcode.push(fxcode1) </script><!-- --><span id="flapjaxsuid1"> </span> > </p>
Not exactly the prettiest, yet quite effective on what it’s trying to do. What I’d rather do, however, is live in native JavaScript to make this happen instead of some of these more interesting abstractions. In some sense, I like tools that stay true to the language and focus on libraries such as jQuery, Moo Tools, etc, although I do find CoffeeScript oddly fascinating.
To back up my argument for a second with an example in native JavaScript, we could do similar using the Reactive Extensions for JavaScript and a little bit of jQuery. In this case, we’ll take the document’s mousemove event and in our Subscribe method call, we set the coords <span> element with our pageX and pageY values from the mousemove event.
$(document).ready(function() { var mouseMove = $(document) .ToObservable("mousemove") .Subscribe(function(event) { $("#coords").html(event.pageX + ", " + event.pageY); }); });
And then we get to our actual HTML which displays our initial values of 0, 0 inside a span before our mouse starts moving.
<div>The mouse coordinates are < <span id="coords">0, 0</span> ></div>
We could of course have tackled this in any number of ways with any number of frameworks, but it is interesting how they differ in tackling the same problem. Let’s take their example of dragging an object around the screen. You can check their code sample here.
Let’s double up our approach and create two <div> elements that we want to drag around the screen.
<div id="dragTarget1" style="background-color: #000000; border: 1px solid #666666; color: #ffffff; padding: 10px; position: absolute; font-family: sans-serif; cursor: move"> Drag Me! </div> <div id="dragTarget2" style="background-color: #0000FF; border: 1px solid #666666; color: #ffffff; padding: 10px; position: absolute; font-family: sans-serif; cursor: move; left: 150px"> Drag Me Too! </div>
Now, let’s do something interesting with it. We’ll create a function called drag which takes a jQuery element and we’ll return the different between our image offset and the position of the mouse. Let’s first define the function and get our mouse up and mouse move events bound.
function drag(dragTarget) { var mouseUp = dragTarget .ToObservable("mouseup"); var mouseMove = dragTarget .ToObservable("mousemove");
Next, on our mouse down, we’ll need to calculate the difference between the mouse position and the offset of our drag target. We will return an object with a left and top properties calculated with the difference between the mouse position and the element’s offset.
var mouseDown = dragTarget .ToObservable("mousedown") .Select(function(event) { return { left : event.clientX - dragTarget.offset().left, top : event.clientY - dragTarget.offset().top }; });
Finally, we need to take our mouse down and bind it to our mouse down with the SelectMany method passing in our image offset. With our mouse move, we need to prevent the default action of the event from happening, which in this case is the text from being selected. We’ll then project the position of the mouse move and create a delta between that and the image offset coming from the mouse down along with returning our drag target for ease of access. And finally, we’ll do all of this until the mouse up event.
return mouseDown .SelectMany(function(imageOffset) { return mouseMove .Do(function(event) { event.preventDefault(); }) .Select(function(pos) { return { target : dragTarget, left : pos.clientX - imageOffset.left, top : pos.clientY - imageOffset.top }; }) .TakeUntil(mouseUp); });
Below is the drag function in its entirety.
function drag(dragTarget) { 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 }; }); return mouseDown .SelectMany(function(imageOffset) { return mouseMove .Do(function(event) { event.preventDefault(); }) .Select(function(pos) { return { target : dragTarget, left : pos.clientX - imageOffset.left, top : pos.clientY - imageOffset.top }; }) .TakeUntil(mouseUp); }); }
Now to hook up the two <div> elements to the drag function and then set the top and left appropriately. To do this, we’ll create an Observer which takes the mouse position and sets the CSS property top and left accordingly. We’ll then call Subscribe on our two elements using the Observer.
$(document).ready(function() { var observer = Rx.Observer.Create( function(pos) { pos.target.css("left", pos.left); pos.target.css("top", pos.top); }); drag($("#dragTarget1")).Subscribe(observer); drag($("#dragTarget2")).Subscribe(observer); });
You can find the source in its entirety here.
Conclusion
Taking lessons from other frameworks gives us some good practice on how to do things with our current library and with the Reactive Extensions for JavaScript and some jQuery, we’re able to do some pretty interesting things such as this simple drag and drop example. That’s just one of the many things we can do with it that I’ll hopefully cover more in the near future. So, download it, and give the team feedback!
What can I say? I love JavaScript and very much looking forward to the upcoming JSConf 2010 here in Washington, DC where the Reactive Extensions for JavaScript will be shown in its full glory with Jeffrey Van Gogh (who you can now follow on Twitter). 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.