Drag and Drop using Reactive Extensions for Javascript

Been a while since I’ve been mean­ing to check out Reac­tive Exten­sions for Javascript, I have been using the .Net ver­sion for a while now and blogged about some of the cool things you can do with the .Net ver­sion.

Although there are many sources where you can learn about it from they don’t have a struc­tured guide to help you get start­ed and some of the info/demo I found were pret­ty out-dat­ed and not in line with the lat­est API. Among the arti­cles I’ve read so far I found the series of arti­cles by Matthew Pod­wysoc­ki on codebetter.com to be most use­ful and def­i­nite­ly worth read­ing through if you want get up to speed with RXJS.

Any­ways, back to the point of the post, I just want­ed to show you how to achieve a sim­ple drag and drop effect using RX, with sup­port for clip­ping so you can’t drag the out­side the par­ent con­tain­er.

Here’s the basic HTML for the demo and with some sim­ple CSS how it looks:

   1: <body>

   2:     <div id="wrapper">

   3:         <div id="draggable"></div>

   4:     </div>

   5: </body>

image

The javascript code for the demo is sim­ple:

   1: <script type="text/javascript" src="js/jquery/jquery-1.4.4.min.js"></script>

   2: <script type="text/javascript" src="js/rxjs/rx.js"></script>

   3: <script type="text/javascript" src="js/rxjs/rx.jQuery.js"></script>

   4:

   5: <script type="text/javascript">

   6:     $(function () {

   7:         var _doc = $(document), _draggable = $("#draggable"), _wrapper = $("#wrapper");

   8:

   9:         // boundary so that the draggable content can't move outside of the its container

  10:         var _parent = _draggable.parent(),

  11:             _parentOffset = _parent.offset(),

  12:             _minLeft = _parentOffset.left,

  13:             _minTop = _parentOffset.top,

  14:             _maxLeft = _minLeft + _parent.width() - _draggable.outerWidth(),

  15:             _maxTop = _minTop + _parent.height() - _draggable.outerHeight();

  16:

  17:         // get the stream of events from the mousedown, mousemove and mouseup events

  18:         var mouseDown = _draggable.toObservable("mousedown"),

  19:             mouseMove = _doc.toObservable("mousemove"),

  20:             mouseUp = _doc.toObservable("mouseup");

  21:

  22:         // get the changes in the X and Y direction as the mouse moves

  23:         var mouseMoves = mouseMove.Skip(1).Zip(mouseMove, function (left, right) {

  24:             return {

  25:                 xChange: left.clientX - right.clientX,

  26:                 yChange: left.clientY - right.clientY

  27:             };

  28:         });

  29:

  30:         // for each mouse down event, get all the subsequent changes in the clientX and

  31:         // clientY values resulting from the mouse move events until mouse up event occurs

  32:         var mouseDrags = mouseDown.SelectMany(function (md) {

  33:             return mouseMoves.TakeUntil(mouseUp);

  34:         });

  35:

  36:         mouseDrags.Subscribe(function (mouseEvent) {

  37:             var oldOffset = _draggable.offset();

  38:

  39:             // change the left and top

  40:             _draggable.css({

  41:                 left: Math.min(Math.max(oldOffset.left + mouseEvent.xChange, _minLeft), _maxLeft),

  42:                 top: Math.min(Math.max(oldOffset.top + mouseEvent.yChange, _minTop), _maxTop)

  43:             });

  44:         });

  45:     });

  46: </script>

As you can see, this exam­ple uses jQuery and includ­ed in the RXJS pack­age is a rx.jQuery.js file which con­tains a num­ber of exten­sion meth­ods to use with jQuery such as the toOb­serv­able func­tion which returns a stream of observ­able val­ues. If this sounds unfa­mil­iar to you I strong­ly rec­om­mend you to read through the first cou­ple of arti­cles by Matthew Pod­wysoc­ki I linked above.

On line 23, I’m cre­at­ed a com­pos­ite event stream by pair­ing mouse move events with their sub­se­quent mouse move event to work out the change in the X and Y direc­tion. To visu­alise the two event streams and the resul­tant mouse­Moves stream:

image

Then on line 36, I’ve sub­scribed an event han­dler for val­ues pushed down to us through the mouse­Moves stream. This event han­dler applies the X and Y changes to the <div> element’s left and top off­set but caps them so that it can only be moved inside its con­tain­er.

Demo

Parting thoughts

I used jQuery to demon­strate RXJS’s exten­sion func­tions for jQuery but you don’t need jQuery in order to use RXJS, in the rx.html.js script file there is a very use­ful Rx.Observable.FromHTMLEvent func­tion which lets you get an observ­able stream of events from any HTML events includ­ing IE-spe­cif­ic events such as ‘oncut’.