JavaScript is a powerful language for creating Web-based applications. It has become stable and mature enough to create programs that rival traditional desktop applications in their stability and in the richness of their features. But JavaScript started out as a language used to add bits of interactivity to otherwise-static Web pages, and it is still used for this purpose.
One of the difficulties in the technique I'm going to show you is structuring your pages properly so that they can interact with JavaScript code. Typically, pages are structured with the JavaScript code in mind. Many times, however, you want to add new, interactive features to an existing page after the fact. Doing so requires a bit of subtlety, because the JavaScript code must traverse the document structure, add bits of code to the proper places, and generally stay out of the way of the existing structure—and of any JavaScript code that already exists on the page. In short, you want the system to be minimally invasive.
This article describes a method for activating a page by allowing you to move sections around. Specifically, you can switch two swappable sections by dragging and dropping one section onto the other.
To activate these sections, you need only add a class
parameter to them and load a JavaScript file. You activate the code by adding
an onload method to the
<body> tag, which starts the code right after
the page finishes loading. The code takes care of the rest.
Note: The source code for the examples in this article are available from the Download section.
In addition, you structure the code using as much abstraction as possible. Different elements of a program are often unnecessarily intertwined, and this is doubly true for UI code. The Swappable system is built from several different pieces, each of which implements a different part of the interactivity. When combined, they work together to achieve a simple, seamless interface, which is crucial for easy experimentation and fine-tuning of UIs.
The Swappable system is easy to use. The Web page designer marks certain sections as swappable. Then, you can click any of the swappable elements and drag them to another swappable element. When you release the mouse button, the two elements are switched.
To make it obvious what's happening, you use a couple of standard GUI cues.
Highlighting the dragged element
When you first click a swappable element, a translucent rectangle appears
under the cursor. Created by the coveringDiv()
function, this rectangle covers the element exactly. It is actually this
rectangle that you drag to another element. While you're dragging,
only the translucent rectangle moves—the original element
stays put until the mouse button is released.
Another important cue is to clearly identify which element you are dragging to. As you move the translucent rectangle around, the cursor moves over various swappable elements. When the cursor is over a swappable element, that element is highlighted with another translucent rectangle. This highlight makes it clear that that element is the drag target. When you release the mouse button, the dragged element and the drag target switch places, and all translucent rectangles disappear until the next swap.
As mentioned earlier, you want your code to be minimally invasive. This means that you don't want the page designers—working in HTML or XML—to have to deal with the Swappable system. That's not their job.
You only require that the page have three things:
- A JavaScript tag
- An
onloadmethod in the<body>tag - Swappable regions marked as swappable
You must place the following tag at the top of the page file:
<script src="rearrange-your-page.js"></script> |
This tag is loaded early in the loading process, but it won't be executed
until the body onload function is called.
This method causes the swappable system to be invoked when the entire page
is loaded. This is important, because the first thing the code does is
search through the page for swappable elements. Therefore, you want to
make sure that they are already loaded. The body onload
method should be as shown in Listing 1.
Listing 1. The body onload handler
<body onload="swappable_start();">
... rest of page
</body>
|
Swappable regions marked as swappable
Each region you want to be swappable must be marked as such with a
class parameter. This is really the only thing
that page authors or designers need to worry about, because they'll have to
add this parameter to every such section. See Listing 2.
Listing 2. Annotating a div with the swappable class
<div class='swappable'>
lorem ipsum lorem ipsum
</div>
|
Finding the swappable sections
The first thing your code has to do is find the sections of the page that are to
be activated. As mentioned earlier, this only requires that the tag surrounding
this section have a class parameter. To find these
sections, you must find all tags that have class
swappable. This function is not part of the standard DOM libraries, but it's
easy enough to implement. Listing 3 shows an example implementation.
Listing 3. An implementation of getElementsByClass()
// By Dustin Diaz
function getElementsByClass(searchClass,node,tag) {
var classElements = new Array();
if ( node == null )
node = document;
if ( tag == null )
tag = '*';
var els = node.getElementsByTagName(tag);
var elsLen = els.length;
var pattern = new RegExp("(^|\\\\s)"+searchClass+"(\\\\s|$)");
for (i = 0, j = 0; i < elsLen; i++) {
if ( pattern.test(els[i].className) ) {
classElements[j] = els[i];
j++;
}
}
return classElements;
}
|
Programs are built by combining pieces of functionality into a coherent whole. There are as many ways to do this as there are programmers, but as a rule, it is good to build programs from lots of small pieces rather than a few big pieces. Each small piece should do one thing and have clear semantics.
Such building is difficult to do, however, when programming GUIs. Good GUIs have to coordinate many interface elements and combine their behaviors into an overall behavior that works intuitively. Event-based systems often wind up being monolithic sets of callbacks united by complicated interactions. Modular interactive elements are tricky to create.
The Swappable code attempts to use modular interactive elements. Earlier, I mentioned that there were two major interactivity elements in the Swappable system: highlighting the dragged element and highlighting the drag target. In the code, these two elements are implemented separately.
This is a good example of what is tricky about modularizing interactivity. As the description of the Swappable interface states, these two interactivity elements are very much intertwined. The highlighting and unhighlighting all happen during a single mouse gesture, and each one happens in response to different aspects of the mouse input. If these two elements were implemented as one monolithic piece of code, it might be a bit tricky to read, because there are several things going on at the same time.
To make the GUI implementation modular, I use something I call a drag handler. This is analogous to the event handler built into GUI systems. Although an event handler handles a single event of some kind, a drag handler handles the entire drag-and-drop process. A drag handler is an example of a handler that deals with a sequence of events rather than a single event. A skeleton drag handler is shown in Listing 4.
Listing 4. A skeleton drag handler
{
start:
function( x, y ) {
// ...
},
move:
function( x, y ) {
// ...
},
done:
function() {
// ...
},
}
|
This drag handler is an object with three methods: start,
move, and done. When you initiate a drag-and-drop action, the start method is called
and is passed the coordinates of the click. As you move the cursor around, the
move method is called repeatedly, again passing the
current coordinates of the cursor. Finally, when you release the mouse button,
the done method is called.
The Swappable system uses two different drag handlers simultaneously, which is how you can cleanly deal with two different aspects of the interaction, even though the two aspects have a complicated relationship. It would not be appropriate for either interaction to be a part of the other one. Instead, you want to be able to take two such interactions and seamlessly use them at the same time.
The first of the two drag handlers is the rectangle_drag_handler.
This handler is responsible for moving the translucent rectangle that
represents the element being dragged. Listing 5 shows the start
method.
Listing 5. The rectangle_drag_handler handler
function rectangle_drag_handler( target )
{
this.start = function( x, y ) {
this.cover = coveringDiv( target );
make_translucent( this.cover, .6 );
this.cover.style.backgroundColor = "#777";
dea( this.cover );
this.dragger = new dragger( this.cover, x, y );
};
// ...
}
|
The start method creates the translucent rectangle
and passes it to another object called a dragger.
A dragger is an object that moves a DOM element around in response
to a moving cursor. You pass the current cursor coordinates to the dragger,
and it updates the dragged object to move along with the cursor.
The move method updates the dragger, as seen in
Listing 6.
Listing 6. Updating the dragger
this.move = function( x, y ) {
this.dragger.update( x, y );
};
|
Finally, the done method, seen in
Listing 7, removes the translucent rectangle, because the
drag-and-drop process is now over.
Listing 7. The rectangle_drag_handler done method
this.move = function( x, y ) {
this.done = function() {
this.cover.parentNode.removeChild( this.cover );
};
}
|
You now have to find some way to use your two drag handlers at the same time. This is easily
done with the compose_drag_handlers() function, which
takes any two drag handlers and combines in them into one combined drag handler.
You can use this combined drag handler anywhere you would use a regular drag
handler, and the result is that the behaviors of the two original drag handlers are
seamlessly combined.
The compose_drag_handlers() function is easy to write. It
looks like a drag handler, but every method calls the corresponding method of each
of the two original drag handlers. The function is shown in Listing 8.
Listing 8. compose_drag_handlers()
function compose_drag_handlers( a, b )
{
return {
start:
function( x, y ) {
a.start( x, y );
b.start( x, y );
},
move:
function( x, y ) {
a.move( x, y );
b.move( x, y );
},
done:
function() {
a.done();
b.done();
},
}
}
|
As you can see, the drag handlers a and
b are combined into a single combined drag handler. Calling,
for example, the start() method of the combined handler
just calls a.start(), and then b.start().
You call compose_drag_handlers in a setup function called
prepare_swappable(), shown in Listing 9.
Listing 9. The prepare_swappable() function
function prepare_swappable( o )
{
swappables.push( o );
var sdp = new rectangle_drag_handler( o );
var hdp = new highlighting_drag_handler( o );
var both = compose_drag_handlers( sdp, hdp );
install_drag_handler( o, both );
}
|
Among other things, this function creates a rectangle_drag_handler
and a highlighting_drag_handler for the swappable
element, and then combines them into a combined drag handler. Finally, this
combined drag handler is activated for the element by calling
install_drag_handler(), described in the next two
sections.
Installing mouse handlers safely
Unlike a regular event handler, a drag handler handles multiple events. Despite this, however, it still needs to be attached to an object, just like a regular event handler.
Installing event handlers of any kind can be tricky, because there's always the chance that the elements you are modifying already have event handlers installed in them. If you replace those, you might change the way the page behaves.
To avoid this, you use a utility function called install_mouse_handlers(),
shown in Listing 10.
Listing 10. The install_mouse_handlers() function
function install_mouse_handlers( target, onmouseup, onmousedown, onmousemove )
{
var original_handlers = {
onmouseup: target.onmouseup,
onmousedown: target.onmousedown,
onmousemove: target.onmousemove
};
target.onmouseup = onmouseup;
target.onmousedown = onmousedown;
target.onmousemove = onmousemove;
return {
restore: function() {
target.onmouseup = original_handlers.onmouseup;
target.onmousedown = original_handlers.onmousedown;
target.onmousemove = original_handlers.onmousemove;
}
};
}
|
The install_mouse_handlers() function adds the
specified mouse handlers to the specified object. It also returns an object
that you can use to restore the original handlers. This way, when a
drag-and-drop sequence ends, you can call the restore()
function to put things back the way they were before the drag-and-drop
sequence started.
The drag handler for a drag-and-drop operation uses all three mouse handlers:
onmousedown, onmouseup,
and onmousemove. However, at first, you only
want to install the mousedown handler, because
you're still waiting for the click that initiates the drag-and-drop
sequence.
When that click comes, you then want to install the
mousemove and mouseup
handlers. Also, at this point, you no longer need the mousedown
handler, because you have already clicked. It is removed and will be
restored when the drag-and-drop sequence is done. The initial
mousedown handler is shown in
Listing 11.
Listing 11. The initial mousedown handler
var onmousedown = function( e ) {
var x = e.clientX;
var y = e.clientY;
p.start( x, y );
var target_handler_restorer = null;
var document_handler_restorer = null;
var onmousemove = function( e ) {
var x = e.clientX;
var y = e.clientY;
p.move( x, y );
};
var onmouseup = function( e ) {
p.done();
target_handler_restorer.restore();
document_handler_restorer.restore();
};
target_handler_restorer =
install_mouse_handlers( target, onmouseup, null, onmousemove );
document_handler_restorer =
install_mouse_handlers( document, onmouseup, null, onmousemove );
e.stopPropagation();
return false;
};
|
When you initiate a drag-and-drop sequence and invoke this handler, it
creates onmousemove and
onmouseup handlers and installs them in the
target element. It uses an install_mouse_handlers(),
of course, so it will be easy to uninstall them later.
Note also that you install these handlers in the document
object. This is crucial, because the user might drag the cursor all over the
page during the drag-and-drop sequence. If the mouse moves outside the
swappable elements—and it very likely will—you still want
to receive these events. Again, you use
install_mouse_handlers() so that you can restore
them later.
I've explained a lot of different classes and functions in this article. Each one is fairly simple on its own, but it's also important to see how they work together.
The following list is a summary of the entire drag-and-drop process:
- You click a swappable element.
- The element's
onmousedownhandler is invoked, which installsonmousemoveandonmouseuphandlers. - As you move the mouse, these handlers are invoked.
- The handlers, in turn, invoke the drag handler that you installed earlier.
- The drag handler is actually a composite drag handler, combining the effects of two different drag handlers.
- One of the drag handlers,
rectangle_drag_handler, is responsible for attaching to the cursor a translucent rectangle that represents the element being dragged. - The other drag handler,
highlighting_drag_handler, is responsible for highlighting the swappable elements over which the cursor passes to show where you are permitted to drag the element. - When you release the mouse button over a destination element,
the
done()method ofhighlighting_drag_handlerswaps the two elements. The drag handlers are uninstalled, leaving the originalonmousedownhandlers behind, ready to start the next drag-and-drop sequence.
The drag-and-drop operation is relatively simple, but it does involve several interacting processes for tracking the user's input and providing immediate feedback throughout the process. This article demonstrates a way to build the complete GUI by combining modular interactive elements into a cohesive whole.
There are several advantages to this approach. Because the code is modular, it is easier to write and maintain. None of the functions or classes are more than 40 lines of code long, and they're often shorter.
Each of the interactive elements implements a typical GUI process or effect, so it's easy to imagine that they might be usable in another context. A sufficiently rich library of such interactive elements could be developed that would make it easy to build sophisticated UIs by snapping parts together.
| Description | Name | Size | Download method |
|---|---|---|---|
| Source code, with example.1 | rearrange-your-page-src.zip | 181KB | HTTP |
Information about download methods
Note
- This contains the source code for the Swappable library and an example page that uses it.
Learn
-
"Javascript Closures" is
an excellent article about closures, which are central to the kind of code discussed in this article.
-
"JavaScript: DHTML
API, Drag & Drop for Images and Layers" is another example of
drag-and-drop function in JavaScript code.
-
Browse the technology
bookstore for books on these and other technical topics.
-
IBM
technical events and webcasts: Stay current with developerWorks' technical
events and webcasts.
Get products and technologies
-
getElementsByClass()is a good example of a useful function that a lot of people have written versions of.
Discuss
-
Check out developerWorks blogs
and get involved in the developerWorks
community.
Greg Travis is a software engineer at Google. He has been a Web programmer, an independent contractor, and a game developer. You can reach Greg at mito@panix.com.
Comments (Undergoing maintenance)





