Integrating Drag and Drop

Integrating Drag and Drop in your Channels

Using DHTML, it is possible to drag and drop any element on the screen. Walter Zorn has written an excellent Javascript Library to support drag and drop (Walter Zorn). I have used this libary to drag and drop in several applications.

To test it, go to iState Portal Demo Site, login using the instructions on the guest page and:

  • click Layout to change your preferences. You may drag and drop tabs, columns and channels. You may download the DnDPreferences Theme from the Clearinghouse.
  • navigate to the Personal Portfolios channel and click Manage your Repository. You may move files to folders by dragging and dropping them.

Integration Steps

Follow these steps to integrate drag and drop:

  1. Download the Walter Zorn library
  2. Put the Javascript Library in a known location on the Portal. For example, I store it in the DnDPreferences directory:
    media/edu/iastate/ait/theme/wz_dragdrop.js.
  3. Include a <script type="text/css>@import url('media/edu/iastate.ait/theme/wz_dragdrop.js');</script> in your channel code
  4. Identify the HTML objects with IDs and make them layers, either by using a CSS property of position: absolute or position: relative
  5. Insert the Javascript to register the HTML objects and handle the events to drag and drop the objects

Example: Dragging a Channel

This example is taken from the DnDPreferences Theme. The technique is to capture the drop event, determine the channel that was dragged and which channel it was dropped on. Give the dragged channel ID and the dropped channel ID, Aggregated layouts can be called to move the channel.

In DndPreferences.xsl, the following code is used to reference the Walter Zorn Javascript library as well as the DnDPreferences code to manage the windows. Note that the definitions must appear at the beginning of the body.

DnDPreferences.xsl Javascript Imports
<body leftmargin="0" topmargin="0" marginheight="0" marginwidth="0">
    <xsl:attribute name="id">body-<xsl:value-of select="translate(//focusedTab//@name,$uppercase,$lowercase)"/>
    </xsl:attribute>
    <!-- this needs to be at the body start -->
    <xsl:if test="$mode = 'preferences' and @userLayoutRoot = 'root'">
        <script type="text/javascript" src="{$mediaPath}/common/scripts/wz_dragdrop.js"/>
        <script type="text/javascript" src="{$mediaPath}/common/scripts/al_dnd.js"/>
    </xsl:if>
....

The actual channel definitions are based on Nested Divs but you can change the code to meet your local theme.

DnDPrefereces.xsl Channel HTML
<xsl:template match="channel">
    <div id="{concat('channelID',parent::node()/@ID,':',@ID,':',following-sibling::channel/@ID)}">
        <xsl:if test="$mode = 'preferences'">
            <xsl:attribute name="class">channel-preference</xsl:attribute>
        </xsl:if>
        <div class="channel-window-outside">
            <div class="channel-window-inside">
                <div class="channel-header">
                    <div>
                        <h1>
                            <xsl:attribute name="id">titleID<xsl:value-of select="@ID"/></xsl:attribute>
                            <xsl:value-of select="@title"/>
                        </h1>
                        <span><xsl:call-template name="channelRestrictions"/></span>
                    </div>
                 </div>
                 <div class="channel-content-border">
                     <xsl:attribute name="id">channelID<xsl:value-of select="@ID"/></xsl:attribute>
                     <div class="channel-content-body">
                         <xsl:copy-of select="."/>
                     </div>
                  </div>
                  <div class="channel-footer">
                      <img src="{$mediaPathSkin}/../backgrounds/channel-bottomleft.gif" alt="" width="10" height="10" border="0"/>
                  </div>
            </div>
        </div>
    </div>
</xsl:template>

Note that the outer DIV wrapper has an unique ID name defined for each channel. In this case, the ID encodes the channel ID and its next sibling.

The following Javascript is included at the bottom of the page after all the objects (i.e. channels) are defined:

Javascript Calls to wz_dragdrop.js
<xsl:if test="$mode = 'preferences' and @userLayoutRoot = 'root'">
    <script type="text/javascript">
        SET_DHTML(CURSOR_HAND,NO_ALT,RESET_Z);
	<xsl:for-each select="/layout//channel">
            ADD_DHTML('<xsl:value-of select="concat('channelID',
                             parent::node()/@ID,':',@ID,':',following-sibling::channel/@ID)"/>');
         </xsl:for-each>
         <!-- other code appears here to add columns and tabs -->
    </script>

The SET_DHTML initializes the data structures and lets you set global parameters like cursors and other properties. See the Zorn site for details.

For each moveable object, an ADD_DHTML must be called to reference its ID. Additional parameters can also be provided to control the display. For example, + HORIZONTAL can be added to the node ID to restrict movement to horizontal motion (not shown in above code).

Finally, you need to write Javascript functions to capture when the object is clicked (my_PickFunc), dragged (my_DragFunc) and dropped (myDropFunc). All objects with IDs added above are available in the dd.elements array. The code for moving the channel is

Javascript DnD Code
function my_DropFunc() {
    var drag = dd.elements[dd.obj.name]; // reference to dragged item
    var channels = new Array();
    for (var i = 0; i < dd.elements.length;i++) {
        var elem = dd.elements[i];
        // pick the channels that are in the same column as drop point.  dd.e is the drop event 
        if (elem.name.indexOf('channelID') == 0 && dd.e.x > elem.defx && dd.e.x < (elem.defx + elem.w)) {
            if (drag == elem && dd.e.y >= elem.defy && dd.e.y < (elem.defy + elem.h)) {
                // delete icon may have been clicked so ignore
                drag.moveTo(drag.defx, drag.defy); // put it back where it was
                return;
            } else
                channels.push(elem);
            }
        }
        channels.sort(sortY);
        // look for first channel that is below drop point
        for (var i = 0; i < channels.length;i++) {
            var elem = channels[i];
            if (dd.e.y < elem.defy) {
                // find channel that will follow dragged channel. Back up 1 if not first in column
                if (i > 0) { elem = channels[i-1]; }
                var dragids = drag.id.substring(9).split(':');
                var dropids = elem.id.substring(9).split(':');
                var nextID = dropids[1];
                makeURL('move_target',dragids[1],dropids[0],nextID);  // execute URL to move
                return;
            }
        }
    }
    // channel dropped below last channel in column
    var dragids = drag.id.substring(9).split(':');
    var last = channels[channels.length-1];
    var dropids = last.id.substring(9).split(':');
    var nextID = (dd.e.y > (last.defy + last.h))?dropids[2]: dropids[1];
    makeURL('move_target',dragids[1],dropids[0],nextID);
    return;
}

Note how each element has a number of parameters defined including its initial position (defx,defy), current position (x,y) and size (w,h). Also, the drop event also defines its position (x,y).

Conclusion

Walter Zorn has provided a wide variety of functions to move, resize, animate and clone objects and it is relatively easy to add functions to themes, file managers and other interfaces. Give them a try!