WebReference.com - Part 2 of Chapter 6 from Dynamic HTML: The Definitive Reference, 2nd Edition. From O'Reilly (7/8).
Dynamic HTML: The Definitive Reference, 2E. Chapter 6: Scripting Events
The final example in this chapter, Example 6-2, demonstrates how event bubbling lets document-level event handlers control dragging positioned elements on the page. To provide legacy support for readers of the first edition of this book, the code includes branches that accommodate the unique requirements of Netscape Navigator 4, whose layer objects may be positioned dynamically. While Navigator 4 does not employ event bubbling, it does allow for the equivalent of the W3C's event capture mechanism, albeit switched on with very different syntax. But all event processing takes place at the same node level for all browsers. In truth, if the dragging operation were being implemented in just one object model, the scripts and approach to the dragging control would be tailored to take advantage of the event and object features of that model. But the goal here is a completely cross-browser implementation that supports any browser capable of positioned elements. To that end, the code relies on a handful of routines from the DHTML API described in Chapter 4. Although Example 6-2 links in the entire library, you should also consider creating a separate
.js library with a subset of the entire API needed to support the dragging operations, and thus minimize the amount of code that travels to the client.
The dragging system implemented in this example has a simple design behind it. Three mouse events--
onmouseup--control the action. Event handlers for all three events are assigned to the document node. Navigator 4 grabs the events before they reach their targets, while the IE and W3C event models wait for the events to bubble up from their targets.
Each event type has a specific role to play. The
onmousedown event handler (the
engage() function) validates that the event occurred on a draggable item and sets three global variables that the other event handlers will use. One variable,
selectedObj, is a reference to the element that will be dragged. The other two variables,
offsetY, preserve the
onmousedown event coordinates within the draggable element so that as the user moves the cursor to drag the element, the cursor maintains its spatial relationship within the element. The
onmousemove event handler (
dragIt()) keeps the element in position with the moving cursor until the
onmouseup event fires. At that point, the
release() function removes the reference from
selectedObj. Any time that variable is
onmousemove events that occur atop the elements don't affect their positions.
Assigning the event handlers to the document node offers two significant advantages. First, a mere three event handler assignments take care of as many draggable items as you want to place on the page. Second, while the user drags the element, element rendering may not refresh as quickly as the cursor moves, preventing the
onmouseup events from firing on the draggable elements. But when the
engage() function "switches on" dragging for a particular element, all
onmouseup events for other elements bubble up to the
document level (unless they are explicitly canceled), and the
release() functions do their jobs because the dragging mode for the selected object is still switched on. As long as the user keeps the mouse button down, the draggable element will catch up to the cursor position.
All the dragging event handlers are assigned as properties in an
init() function invoked by the
onload event handler. The only platform-specific process taking place here involves setting the
document.captureEvents() method to grab all mouse down and mouse move events that come in from Navigator 4.
The draggable elements in this example are two absolute-positioned
div elements that contain
img elements. The user can click on either image and drag that image (and its
div) around the page. Wrapping the image with a
div element helps with a couple of fine points about this example. For one, absolute-positioned
span elements are better behaved in Navigator 4 than positioning other kinds of elements (
layer elements are the best behaved, but they are unique to Navigator 4). But more important is demonstrating one way to assure that event targets inside a positioned element (the
img elements are the targets here) translate into a reference to the container, because the container is the element that gets positioned (while its contents go along for the ride).
Notice in Example 6-2 how the IDs of the image event targets and their divs are related (
imgAWrap). The filtering in the
setSelectedElem() function uses the fact that the
img element targets have name and
src properties assigned to them. If that's the case for a particular
onmousedown event, then the ID for the div wrapper gets assembled from the target's name and the "Wrap" add-on. This approach certainly works, but its looseness is necessitated by the support required for Navigator 4's immature DOM. If this were being written for IE 5 (or later) and Netscape 6 (or later), a much more generalizable solution is possible and preferable. For example, you could assign an identifier to the
class attribute of the
img elements (e.g.,
draggable), and let
setSelectedElem() simply look for the event target's
className property that equals that identifier. If there's a match, you could immediately set the global
selectedObj variable to the event target's
selectedObj = (target.className == "draggable") ? target.parentNode : null;
No other element referencing or naming games would be necessary.
Notice, too, that the
setSelectedElem() function sets the stacking order of the element to an arbitrarily high number. You want a selected element to be atop all its peers as the user drags it around the screen.
Coordinate systems play a significant role in scripting the drag process. Ideally, the element should track from the point where the user clicks inside the element. This means that the location (top left corner) of the element must be offset (up and to the left) from the cursor position by the number of pixels of the click offset within that element. This information isn't as easy to come by as you might think. Not all event models report the offsets within a positioned container; those that do need further adjustments for document scrolling or inherent bugs. The tripartite branch in the
engage() function takes care of the measurements for three event model implementations:
Navigator 4 and Netscape 6 (the latter supports the
pageYproperties of the event object, even though they are not part of the W3C event model)
IE, including the corrections for scrolling and occasional erroneous values in IE/ Mac
A pure W3C event model, which Opera follows in this case, and Netscape 6 or later would also follow if the first branch were absent
Offset values are stored as global variables in Example 6-2, so that the dragging action can use them for proper placement of the element under the cursor.
Making the element track the cursor in the
dragIt() function also requires some calculation. Using the
shiftTo() function from the DHTML API, the script sets the location of the element within the page (or client) space after each mouse movement.
Despite the amount of object detection branching taking place in Example 6-2, if you trimmed the scripts to work only in IE 5 (or later) and Netscape 6 (or later), and employed the
parentNode tips described earlier, you could easily make the dragging functionality into a reusable library, nearly independent of the specific content on the page. The only care you'd have to exercise is assigning your chosen class identifier to the draggable image or area of the positioned element, such as a pseudo-titlebar of a "floating" palette. This example also works as-is only with positioned elements that use the
document element (depending on your standards-compatibility mode) as the positioning context. Modifications would be needed to nest the positioned elements in other contexts.
Example 6-2 Dragging elements around the window
Created: September 9, 2002
Revised: September 9, 2002