I’ve been working on a Javascript-driven Django application pretty much non-stop for the last week or so. I’ll leave my experiences with the Django framework for another post1. There’s more than twice as much Javascript code in this application than there is Python code, so I can’t really claim that I know Django yet, to be honest.
Anyway, the application I am working on is similar to Google Calendar in terms of user interface. Basically, it allows VTK’s2 praesidium and volunteers to indicate when and how they wish to help for every activity they organise. For each activity, a list of shifts is presented in a timetable. Usability is the main concern here, so everything is mouse-driven and easy to use. At least, that’s what it is intended to be. There’s a screenshot here, which should help to get the general idea. The most interesting part is the administration interface (for the creation of the shift lists), which allows for shifts to be drawn, resized and moved around on the grid with the mouse. jQuery and its many plugins (jQuery UI in particular) have made it relatively easy to get this done.
However, there was one thing that bothered me: DOM elements only receive mouse events when the mouse pointer is within their bounds. This is expected behaviour, but sometimes it’s nice to be able to have a certain element capture the mouse (i.e. keep receiving mouse events as long as a button is pressed, regardless of the position of the pointer), for example when dragging to scroll. Normally, when you accidentally move the mouse pointer outside of the scrollable area, the scrolling will stop because the DOM element is no longer receiving the mousemove event. In addition, this means that the element will never receive the mouseup event. I often perform cleanup actions in a mouseup handler; in this example, the mouse cursor might be changed during dragging, so it has to be reset afterwards. If the event is never triggered, this leaves the application in a “dirty” state.
For my student job last summer, I used Silverlight 2, which allows you to call CaptureMouse3 on any interface element. After this, all mouse events will be sent to this element, regardless of where they occur. This behaviour can be removed again with ReleaseMouseCapture. This makes it easy to create user friendly mouse-driven applications; just call the former in the mousedown handler, and the latter on mouseup.
Such a thing, at least to my knowledge, does not exist for web pages. However, this can be circumvented in a number of ways. For starters, it is interesting to note that Firefox4 always executes mouse event handlers bound to the document element when a mouse button is pressed and its window is active, even when the mouse pointer is actually outside of the window boundaries. This is interesting because it allows for Silverlight’s CaptureMouse behaviour to be mimiced.
Obviously, it makes no sense to bind mousedown handlers to document; the event would always be triggered, and there would be no way to find out which element was actually clicked. However, we could identify the clicked element in the mousedown handler and tuck it away somewhere; the mousemove and mouseup handlers are then able to access this information and realise the desired behaviour, even though they are not bound to the target element.
There are two ways to go about this (at least afaik, there might be more). One is to have each of the mousedown handlers store the element in a variable, which the other handlers check whenever they are invoked (e.g. whenever the corresponding event is triggered on the document element). This variable need not be global; it just has to be part of a scope that is available in all handler functions. You can easily prevent it from being global by wrapping your code like this:
(function() { // your code goes here })();
An anonymous function is created and it is executed immediately. It serves no purpose except to create a local lexical scope; any variables declared inside the function (using the var keyword) are not available outside it. Note that this practice is recommended to jQuery plugin developers as well. But I digress.
Here’s an example of how this technique could work:
$(document).ready(function() { /* The code is already wrapped inside a function here, so we need not use the previously mentioned technique to protect the "targetElement" variable from being exposed globally. */ var targetElement = null; $("#div1, #div2").mousedown(function() { targetElement = this; }); $(document).mousemove(function() { if (targetElement.id == "div1") { // handler for #div1 } }); $(document).mousemove(function() { if (targetElement.id == "div2") { // handler for #div2 } }); // same for mouseup });
The handlers could also be combined into a single one, containing a (potentially) large if-else structure, or maybe even a switch on targetElement.id. This all looks a bit awkward though, to me at least.
Another way to go about this is by binding the mousemove and mouseup events inside the mousedown handler (and unbinding them again on mouseup). This is the technique I’m currently using in the application I mentioned earlier. You can define the dynamically attached handlers inside the mousedown handler5, which gives them access to its scope; this eliminates the need for shared variables.
Here’s how it looks:
$(document).ready(function() { $("#div1").mousedown(function() { var target = this; var moveHandler = function() { // handler for #div1 }; var upHandler = function() { // handler for #div1 $(document).unbind("mousemove", moveHandler); $(document).unbind("mouseup", upHandler); }; $(document).mousemove(moveHandler); $(document).mouseup(upHandler); }); // same for #div2 });
To be honest, I’m not sure which of these performs best. The first method has the advantage of only requiring binding at the start, whereas the second requires the mousemove and mouseup handlers to be rebound on every mousedown event. For the second method, on the other hand, only a single handler is ever bound to the mousemove and mouseup events (however, this is also true for the first, if all handlers are combined). It does not require any (potentially expensive) “identity checks” on the target element, either. Testing the performance of these different techniques would probably yield some interesting results, so I might do that later.
Both of these approaches contain a lot of boilerplate code. It would be nice if we could abstract this into a plugin. Thanks to jQuery’s extensibility, we can accomplish this fairly easily. I implemented the second of the described techniques, because it was easiest. I’m not sure how to go about implementing the first, because of the required identity checks.
So here it is:
- jquery.mousecapture.js – the plugin; tested with jQuery 1.3.1, but it should work with older versions.
- A simple demo – try it yourself! Start dragging on the black box, and move the mouse away from it; the move and up handlers will still be executed properly.
You use it like this:
$("#test_div").mousecapture({ "down": function(e, s) { s.data = Date.now(); $(this).html("mousedown triggered on " + s.data); }, "move": function(e, s) { $(this).html("mousemove triggered on " + Date.now() + "capturing since " + s.data); }, "up": function(e, s) { $(this).html("mouseup triggered on " + Date.now() + "capturing since " + s.data); }, });
Please take in account that I wrote the plugin while writing this article, so I haven’t tested it in practice yet. I might refactor the shift management application to use it, though. Feel free to suggest improvements, particularly pertaining to the sharing of data between the different handlers. I’m not too satisfied with how it handles that, but it does the job.
Update: it’s only a few lines of code, but I threw it on GitHub anyway.
Notes
- ↑1 Maybe even throw in a few comparisons with Rails… can you smell the trolls already?
- ↑2 VTK is the fraternity for engineering students at Ghent University.
- ↑3 Check out the MSDN documentation for
CaptureMousefor more information. - ↑4 I haven’t tested this in other browsers, nor on other operating systems (I use ArchLinux), because I am lazy. For now I’m assuming that they will exhibit similar behaviour.
- ↑5 I don’t know if this is a detriment to performance, but it very well might be.


4 Comments
Great post. Have you seen the mouse plugin inside of jQuery UI Core? It’s covers this and a couple other common features and is the basis for all the jQuery UI Interactions: draggable, droppable, resizable, selectable, sortable. It’s not documented on its own (yet) but you could look at any of these plugins for an example of use.
I discovered jQuery UI only a few days ago, when I realised I was going to need a flexible way to implement drag and drop functionality for the application I described. I was looking for jQuery plugins to take care of this. As it turned out, jQuery UI takes care of this as well, and it does so quite elegantly (for some reason I’ve always thought jQuery UI was just a collection of widgets).
I haven’t taken the time to look at jQuery UI’s source code (or that of jQuery itself, actually), because I’m trying to finish this project as soon as possible; there isn’t really a deadline, but it’s pretty badly needed.
I will definitely take a look at it. Thanks for your comment!
This is excellent, thank you! It provides a nice solution for icon buttons as well. For example, go to this site and mousedown on one of the two buttons in the “Basic button from different types of markup” section. Before doing the mouseup, move the mouse so that the mouseup occurs outside of the bounds of the button. When you mouseup, you’ll see that the button is still styled with the mousedown style. Your code provides an elegant solution, thanks!
Thanks for letting me know! Good to see I manage to produce something with some kind of practical use every once in a while