Skip to main content

Creating modular interactive user interfaces with JavaScript

Greg Travis (mito@panix.com), Software Engineer, Google
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.

Summary:  Discover a technique that lets you move sections of a Web page using drag-and-drop functions. Different aspects of the interactivity are implemented separately and then composed into a unified whole, allowing for flexible customization that can make your Web users very happy.

Date:  23 Sep 2008
Level:  Intermediate PDF:  A4 and Letter (41KB)Get Adobe® Reader®
Activity:  3788 views

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.

Common acronyms

  • UI: User interface
  • GUI: Graphical user interface
  • HTML: Hypertext Markup Language
  • XML: Extensible Markup Language
  • DOM: Document Object Model

The Swappable system

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 interface

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.

Highlighting the drag target

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.

Activating the system

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 onload method in the <body> tag
  • Swappable regions marked as swappable

A JavaScript tag

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.

Onload method in the body tag

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;
}

Elements of interactivity

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.

Modular interactive elements

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.

Drag handlers

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.

rectangle_drag_handler

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 );
    };
}

Composing drag handlers

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.

Activating a drag handler

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.


Putting it all together

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 onmousedown handler is invoked, which installs onmousemove and onmouseup handlers.
  • 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 of highlighting_drag_handler swaps the two elements. The drag handlers are uninstalled, leaving the original onmousedown handlers behind, ready to start the next drag-and-drop sequence.

Conclusion

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.



Download

DescriptionNameSizeDownload method
Source code, with example.1rearrange-your-page-src.zip181KB HTTP

Information about download methods

Note

  1. This contains the source code for the Swappable library and an example page that uses it.

Resources

Learn

Get products and technologies

  • getElementsByClass() is a good example of a useful function that a lot of people have written versions of.

Discuss

About the author

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)



Trademarks  |  My developerWorks terms and conditions

Help: Update or add to My dW interests

What's this?

This little timesaver lets you update your My developerWorks profile with just one click! The general subject of this content (AIX and UNIX, Information Management, Lotus, Rational, Tivoli, WebSphere, Java, Linux, Open source, SOA and Web services, Web development, or XML) will be added to the interests section of your profile, if it's not there already. You only need to be logged in to My developerWorks.

And what's the point of adding your interests to your profile? That's how you find other users with the same interests as yours, and see what they're reading and contributing to the community. Your interests also help us recommend relevant developerWorks content to you.

View your My developerWorks profile

Return from help

Help: Remove from My dW interests

What's this?

Removing this interest does not alter your profile, but rather removes this piece of content from a list of all content for which you've indicated interest. In a future enhancement to My developerWorks, you'll be able to see a record of that content.

View your My developerWorks profile

Return from help

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=1
Zone=Web development
ArticleID=340375
ArticleTitle=Creating modular interactive user interfaces with JavaScript
publish-date=09232008
author1-email=mito@panix.com
author1-email-cc=

My developerWorks community

Tags

Help
Use the search field to find all types of content in My developerWorks with that tag.

Use the slider bar to see more or fewer tags.

Popular tags shows the top tags for this particular content zone (for example, Java technology, Linux, WebSphere).

My tags shows your tags for this particular content zone (for example, Java technology, Linux, WebSphere).

Use the search field to find all types of content in My developerWorks with that tag. Popular tags shows the top tags for this particular content zone (for example, Java technology, Linux, WebSphere). My tags shows your tags for this particular content zone (for example, Java technology, Linux, WebSphere).

Rate a product. Write a review.

Special offers