id="info"

Enterprise Software

Manage and sort data with drag-and-drop in IE

Are you looking for a grid control that you could use to sort data? Then check out this solution from Phillip Perkins, in which he demonstrates how easy it is to create this functionality by using XML and Internet Explorer's data-binding behavior.

A coworker once asked me about a grid control that he could use to sort data. He was looking for a method by which he could take a parent node's child nodes and move them to a different parent node. I couldn't think of a particular control, but I said that he could easily create this functionality by using XML and Internet Explorer's data-binding behavior. This article outlines the method I used to help my coworker achieve his task.

One of IE's best features is its ability to bind data in XML data islands to particular HTML elements, especially <TABLE> elements. You can easily display hierarchical data by nesting more tables within parent tables. By nesting a table within another table and binding the data to the XML data island, I was able to accomplish my coworker's task easily and quickly.

The appendChild() method of the DOMDocument class (which the XML data island implements) also helps me with this task. I can take a parent node's child node and use the appendChild() method of the new parent node to attach the child node to the new parent. When this happens, the child node is "physically" removed from the previous parent node and added to the new parent node. Above and beyond that, IE automatically updates the bound elements, namely the <TABLE> element. The only thing left to do is add the method by which the user will interact with the data. You can do this with drag-and-drop.

In my table, I need a drag source to drop in the new parent. If I use a <SPAN> element, which is the element that will be bound to the child data, I'll have to select the text within the span to create the TextRange that will become the drag source. However, images make nice drag sources. Each child row will contain an <IMG> tag that will give me the drag-and-drop functionality that I'll need.

In order to allow the <TR> element that's bound to the parent node to become a drop target, I must add event handlers for the drag and drop events. I'll disable the default ondragenter and ondragover event handlers of the row by the returnValue of the event object to false:

<tr . . . ondragenter="window.event.returnValue=false"
ondragover="window.event.returnValue=false">

And, I'll add a custom event handler for the ondrop event. This event handler will be responsible for finding the parent node within the DOM and appending the selected child.

In order to select the child, I have to create an event handler for the ondragstart event of the <IMG> tag. This event handler is responsible for selecting the child node that will be moved.

Now that you have the foundation for the page, I'll create the code:

<html>
<head>
<script language="JavaScript">

var childrenNode = null;

function img_ondragstart(element) {
    var childId = element.parentElement.all.hdnChild.value;
    childrenNode = xmlTest.selectSingleNode("//children[id='" + childId +
 "']");
}

function tr_ondrop(element) {
    if (childrenNode == null) return;
    var dadId = element.all.hdnDad.value;
    var parentNode = xmlTest.selectSingleNode("//parent[id='" + dadId + "']");
    parentNode.appendChild(childrenNode);
    childrenNode = null;
}

</script>
</head>
<body>
<xml id="xmlTest" name="xmlTest">
<root>
    <parent>
        <id>1</id>
        <dad>John</dad>
        <children>
            <id>1</id>
            <child>Mary</child>
        </children>
        <children>
            <id>2</id>
            <child>Sally</child>
        </children>
    </parent>
    <parent>
        <id>2</id>
        <dad>Jim</dad>
        <children>
            <id>3</id>
            <child>David</child>
        </children>
        <children>
            <id>4</id>
            <child>George</child>
        </children>
    </parent>
</root>
</xml>

<table id="tblMain" cellpadding="0" cellspacing="0" border="1"
 dataSrc="#xmlTest">
<tr ondrop="tr_ondrop(this)" ondragenter="window.event.returnValue=false"
 ondragover="window.event.returnValue=false">
    <td colspan="2">
        <input type="hidden" id="hdnDad" dataFld="id">
        <span dataFld="dad"></span>
    </td>
</tr>
<tr>
    <td style="width:50px;">&nbsp;</td>
    <td>
        <table cellpadding="0" cellspacing="0" border="1" dataSrc="#xmlTest"
 dataFld="children">
        <tr>
            <td>
                <img src="reddot.gif" ondragstart="img_ondragstart(this)"
 width="15" height="15">
                <input type="hidden" id="hdnChild" dataFld="id">
                <span dataFld="child"></span>
            </td>
        </tr>
        </table>
    </td>
</tr>
</table><br>

</body>
</html>

If you inspect the tables within the code, you'll notice that the outer table consists of two rows. The first row contains one cell that spans two columns. In this cell, I place the <SPAN> element that binds to the data that I want to display. A hidden <INPUT> element is added for the unique identifier <id/>. I use this value to locate the node within the DOM using XPath.

The second row contains two cells: One is a blank cell that serves as a spacer so you can see the levels of hierarchy better, and the other contains the <SPAN> and hidden <INPUT> elements that I use just like in the previous row. It also contains the <IMG> tag that I use as the drag source.

When you look at the JavaScript, you'll see the two simple event handlers that create the drag-and-drop functionality. The img_ondragstart() function locates the associated <children> node within the DOM and sets a public variable to that node. The tr_ondrop() function first checks to make sure there's a child node to move, and then locates the <parent> node within the DOM. It then appends the child node to the located <parent> node. Then, it resets the public variable for good measure.

The XML data island's structure must follow the conventions used to bind XML data to <TABLE> elements. This means that each row on the outer table is bound to the repeating child nodes of the root node. Each row of the nested table is bound to the repeating child nodes of the node bound to the row of the outer table. To bind the inner table, you must specify the dataFld attribute of the <TABLE> element.

Be sure to check out this example online.

Keep your developer skills sharp by automatically signing up for TechRepublic's free Web Development Zone newsletter, delivered each Tuesday.

Editor's Picks