Dropping

Thus far, this chapter has focused on dragging objects around on the screen. This section wraps up the discussion by focusing in on the dropping part of it all. To get started, let's first take a look at dojo.dnd.Source, a special container class the toolkit provides a drag-and-drop source. A Source can also act as a target for a drop, but as we'll see in a moment, you can also specify a "pure" target with dojo.dnd.Target. While a Source may act as an origin and a destination, a Target may only act as a destination.

Creating a Source is just like creating a Moveable ; you call the constructor function and pass in a node as the first argument and an Object of parameters as the second argument, like so. Table 7-3 lists the relevant methods.

Table 7-3. Creating and destroying a Source

Name

Comment

dojo.dnd.Source(/*DOMNode*/node, /*Object*/params)

Constructor method for creation. Valid values for params are provided in Table 7-1.

destroy( )

Prepares the Object to be garbage-collected.

Table 7-4 summarizes key parameters involved in the creation of a Source object.

Table 7-4. Configuration parameters for Source's params in Table 7-3

Parameter

Type

Comment

isSource

boolean

true by default; if false, prevents drag action from being possible.

horizontal

boolean

false by default; if true, constructs a horizontal layout (inline HTML elements required).

copyOnly

boolean

false by default; if true, always copies items instead of moving them (no Ctrl-key required).

skipform

boolean

false by default; like Moveable, controls whether to make text-based form elements editable.

withHandles

boolean

false by default; when true, allows dragging only by handles.

accept

Array

["text"] by default. Specifies the type of object that can be accepted for a drop.

A very common use for a Source is to eliminate some of the bookkeeping that is involved in dragging and dropping items that are arranged in a list-like format. The following code example illustrates:

<html>
    <head>
        <title>Fun with Source!</title>
        <link rel="stylesheet" type="text/css"
          href="http://o.aolcdn.com/dojo/1.1/dijit/themes/tundra/tundra.css" />
        <link rel="stylesheet" type="text/css"
          href="http://o.aolcdn.com/dojo/1.1/dojo/resources/dojo.css" />
        <link rel="stylesheet" type="text/css"
          href="http://o.aolcdn.com/dojo/1.1/dojo/resources/dnd.css" />
        <link rel="stylesheet" type="text/css"
          href="dndDefault.css" />
        <script
            type="text/javascript"
            djConfig="parseOnLoad:true"
            src="http://o.aolcdn.com/dojo/1.1/dojo/dojo.xd.js">
        </script>

        <script type="text/javascript">
            dojo.require("dojo.dnd.Source");
            dojo.require("dojo.parser");
        </script>
    </head>
    <body>
        <div dojoType="dojo.dnd.Source" class="container">
            <div class="dojoDndItem">foo</div>
            <div class="dojoDndItem">bar</div>
            <div class="dojoDndItem">baz</div>
            <div class="dojoDndItem">quux</div>
        </div>
    </body>
</html>

Although this initial example may not look like much, there is a tremendous amount of functionality packed into it. For starters, notice that the only Dojo class that is directly involved is Source, and to create a container of items that are drag-and-droppable within that container, you simply provide the token dojoType tag and ensure that the element is parsed; like most other examples, the parseOnLoad parameter passed to djConfig takes care of this task.

Next, take a few moments to tinker around with the example. It might be obvious that you can drag-and-drop single items at a time, but it's important to also note the full gamut of functionality that is offered:

  • Clicking selects a single element and results in all other elements becoming unselected.

  • Ctrl-clicking toggles the selection state of an item and allows you to build up multiple items at a time; you can also deselect individual items from a multiple selection situation.

  • Shift-clicking selects a range of elements from the previous-most selection to the current element being clicked. Any selections before the previous-most selection become unselected.

  • Ctrl-Shift-clicking selects a range of elements from the previous-most selection to the current element being clicked, but preserves any selections before the previous-most selection.

  • Holding down the Ctrl key while performing a drop results in the selection(s) being copied. Figure 7-1 illustrates some of these actions.

dnd1 shows an initial selection using Ctrl-click; dnd2 is the result of performing a Shift-click on quux; dnd3 is the result of performing a Shift-Ctrl-click on quux; dnd4 depicts a move operation by dragging without the Ctrl key; and dnd5 shows a copy operation by dragging with the Ctrl key applied

Figure 7-1. dnd1 shows an initial selection using Ctrl-click; dnd2 is the result of performing a Shift-click on quux; dnd3 is the result of performing a Shift-Ctrl-click on quux; dnd4 depicts a move operation by dragging without the Ctrl key; and dnd5 shows a copy operation by dragging with the Ctrl key applied

Pure Targets

As mentioned earlier in the chapter, there are bound to be plenty of times when you'll need to employ a Target that can only act as a destination; once items are placed in it, they may not be moved or reordered. Make the following trivial modification to the previous code listing to see a Target in action.

<body>
    <div dojoType="dojo.dnd.Source" class="container">
        <div class="dojoDndItem">foo</div>
        <div class="dojoDndItem">bar</div>
        <div class="dojoDndItem">baz</div>
        <div class="dojoDndItem">quux</div>
    </div>
    <!-- Items added to targets cannot be removed or reordered -->
    <div dojoType="dojo.dnd.Target" class="container"></div>
</body>

As you may be able to tell by now, a tremendous amount of functionality is wrapped up into just a few lines of code, and although div elements were used for the example, note that other types of standard HTML elements work equally well. Unordered lists via the ul and li elements are a common choice.

Custom Avatars

The small icon that temporarily appears when an item from a Source is being moved around is called an avatar. Although the standard avatar is quite nice, you may want to construct your own at some point. The following code adjustment illustrates how to define custom text for an avatar by overriding the creator method because this method is used to create an avatar representation of one or more nodes. In this particular circumstance, we'll choose to override creator in markup. The layout is also adjusted to a horizontal specification to simultaneously demonstrate how to adjust a layout:

<body>
    <div dojoType="dojo.dnd.Source" horizontal=true class="container">
        <span class="dojoDndItem ">foo</span>
        <span class="dojoDndItem ">bar</span>
        <span class="dojoDndItem ">baz</span>
        <span class="dojoDndItem ">quux</span>

        <script type="dojo/method" event="creator" args="item,hint">
            // override the creator function and return the appropriate type
            var node = dojo.doc.createElement("span");
            node.id = dojo.dnd.getUniqueId(  );
            node.className = "dojoDndItem";
            node.innerHTML = "<strong style='color: red'>Custom</strong>  "+item;
            return {node: node, data: item, type: ["text"]};
        </script>

    </div>
    <div dojoType="dojo.dnd.Target" horizontal=true class="container"></div>
</body>

Note that the arguments passed into creator are item and hint, the actual item being moved and a value specifying a "hint" for the kind of representation of that should be created. Unless you implement your own low-level machinery, hint will always be "avatar". The creator function is expected to return an object representation of item with keys for an actual DOM node, a data representation, and the type of representation. Recall that "text" is the default representation accepted by a Source object.

Drop Events

Subscribing and connecting to events via dojo.subscribe and dojo.connect works just as easy as with Moveable objects. Table 7-5 summarizes public events for pub/sub and connection-style communications, and a code example follows.

Table 7-5. Drop events

Type

Event

Parameters

Summary

subscribe

"/dnd/source/over"

/* Node */ source

Published when the mouse moves over a Source container; the source parameter specifies the container. When the mouse leaves the Source container, another topic is published with null as Source.

subscribe

"/dnd/start"

/* Node */ source

/* Array */ nodes

/* Boolean */ copy

Published when a drag beings. Parameter source specifies the Source container that provides the origin of the drop operations. Parameter copy is true for a copy operation and false for a move operation. Parameter nodes is an array of items involved in the drop operation.

subscribe

"/dnd/drop"

/* Node */ source

/* Array */ nodes

/* Boolean */ copy

Published when a drop occurs (and a drag officially) ends. Parameter source specifies the Source container that provides the origin and destination of the drop operations. Parameter copy is true for a copy operation and false for a move operation. Parameter nodes is an array of items involved in the drop operation.

subscribe

"/dnd/cancel"

N/A

Published when a drop operation is cancelled (for example, when the Esc key is pressed).

connect

onDndSourceOver

/* Node */ source

Called when a mouse moves over a Source container; parameter source specifies the container. When the mouse leaves the Source container, another onDndSourceOver is called again with null as Source.

connect

onDndStart

/* Node */ source

/* Array */ nodes

/* Boolean */ copy

Called when a drag begins. Parameter source specifies the Source container that provides the origin of the drop operations. Parameter copy is true for a copy operation and false for a move operation. Parameter nodes is an array of items involved in the drop operation.

connect

onDndDrop

/* Node */ source

/* Array */ nodes

/* Boolean */ copy

Called when a drop occurs (and a drag officially) ends. Parameter source specifies the Source container that provides the origin and destination of the drop operations. Parameter copy is true if the operation is a copy operation and false for a move operation. Parameter nodes is an array of items involved in the drop operation.

connect

onDndCancel

N/A

Called when a drop operation is cancelled (for example, when the Esc key is pressed).

Go ahead and load up the following full-blown example and use Firebug to inspect the output that occurs from the various topics that we subscribe to and log to the console, and remember that you can drag-and-drop from different Source containers. Figure 7-2 shows the result. Good stuff!

Firebug is great for learning the ropes of drag-and-drop

Figure 7-2. Firebug is great for learning the ropes of drag-and-drop

<html>
    <head>
        <title>More Fun with Drop!</title>
        <link rel="stylesheet" type="text/css"
          href="http://o.aolcdn.com/dojo/1.1/dijit/themes/tundra/tundra.css" />
        <link rel="stylesheet" type="text/css"
          href="http://o.aolcdn.com/dojo/1.1/dojo/resources/dojo.css" />
        <link rel="stylesheet" type="text/css"
          href="dndDefault.css" />
        <script
            type="text/javascript"
            djConfig="parseOnLoad:true"
            src="http://o.aolcdn.com/dojo/1.1/dojo/dojo.xd.js">
        </script>

        <script type="text/javascript">
            dojo.require("dojo.dnd.Source");
            dojo.require("dojo.parser");

            dojo.addOnLoad(function(  ) {
                dojo.subscribe("/dnd/source/over", function(source) {
                    console.log("/dnd/source/over", source);
                });
                dojo.subscribe("/dnd/start", function(source, nodes, copy) {
                    console.log("/dnd/start", source, nodes, copy);
                });
                dojo.subscribe("/dnd/drop", function(source, nodes, copy) {
                    console.log("/dnd/drop", source, nodes, copy);
                });
                dojo.subscribe("/dnd/cancel", function(  ) {
                    console.log("/dnd/cancel");
                });
            });
        </script>
    </head>
    <body>
        <div id="source1" dojoType="dojo.dnd.Source" class="container">
            <div class="dojoDndItem">foo</div>
            <div class="dojoDndItem">bar</div>
            <div class="dojoDndItem">baz</div>
            <div class="dojoDndItem">quux</div>
        </div>
        <div id="source2" dojoType="dojo.dnd.Source" class="container">
            <div class="dojoDndItem">FOO</div>
            <div class="dojoDndItem">BAR</div>
            <div class="dojoDndItem">BAZ</div>
            <div class="dojoDndItem">QUUX</div>
        </div>
    </body>
</html>

All it takes to demonstrate some connections is a different addOnLoad function. Note that because we need to have a reference to the Source that is created (not the DOM node), we need to programmatically create the Source instead of relying on the parser to instantiate widgets that are defined in markup. Substitute the following, turn off djConfig's parseOnLoad flag, and take a look at the Firebug console once again:

dojo.addOnLoad(function(  ) {
        //keep a reference to the Source to use for connecting.
        var s1 = new dojo.dnd.Source("source1");

        dojo.connect(s1, "onDndSourceOver", function(source) {
            console.log("onDndSourceOver for", s1, source);
        });
        dojo.connect(s1, "onDndStart", function(source, nodes, copy) {
            console.log("onDndStart for ", s1, source, nodes, copy);
        });
        dojo.connect(s1, "onDndStop", function(source, nodes, copy, target) {
            console.log("onDndStop for",  s1, source, nodes, copy, target);
        });
        dojo.connect(s1, "onDndCancel", function(  ) {
            console.log("onDndCancel for ", s1);
         });
});

Scripting Droppables

While the previous example demonstrated that you could use the Source constructor function to make a node droppable, there is considerably more functionality you can achieve via scripting. Table 7-6 summarizes the functionality that Selector, a lower level class in dojo.dnd, offers. Because Source inherits from Selector, these functions are directly available to you though Source, although you might very well find uses for Selector in and of itself.

Table 7-6. Selector API

Method

Comment

getSelectedNodes( )

Returns an Array of the selected nodes.

selectNone( )

Deselects all of the nodes.

selectAll( )

Selects all of the nodes.

deleteSelectedNodes( )

Deletes all selected nodes.

insertNodes(/* Boolean */ addSelected, /* Array */ data, /* Boolean */ before, /* Node */ anchor)

Inserts an Array of nodes, optionally allowing them to be selected via addSelected. If no anchor is supplied, nodes are inserted before the first child of the Selector. Otherwise, they are inserted either before or after the anchor node according to the value of before.

destroy( )

Prepares the object for garbage collection.

onMouseDown(/* Object */ event)

Can be connected to via dojo.connect for detecting onmousedown events, although higher-level onDnd methods should first be considered. Parameter event provides standard event info.

onMouseUp(/* Object */ event)

Can be connected to via dojo.connect for detecting onmouseup, although higher-level onDnd methods should first be considered. Parameter event provides standard event info.

onMouseMove(/* Object */ event)

Can be connected to via dojo.connect for detecting mouse motion, although higher-level onDnd methods should first be considered. Parameter event provides standard event info.

onOverEvent(/* Object */ event)

Can be connected to via dojo.connect for detecting when the mouse enters the area, although higher-level onDnd methods should first be considered. Parameter event provides standard event info.

onOutEvent(/* Object */ event)

Can be connected to via dojo.connect for detecting when the mouse leaves the area, although higher-level onDnd methods should first be considered. Parameter event provides standard event info.

Get Dojo: The Definitive Guide now with the O’Reilly learning platform.

O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.