I wanted to create a parent/child relationship UI mechanism similar to those found on the infamous MCSD Solution Architecture exam. In some of those exam questions you are asked to relate items such as which table columns belong in which table. The table columns being the child items and the tables being the parent items. I started down the path of using ASP.NET listboxes, but the round-trip was somewhat irritating. I wanted to see what I could come up with using DHTML.

What I ended up doing was creating two scrollable DIV tags.
.MyDiv
{
BORDER-RIGHT: #6f8cde 1px solid;
BORDER-TOP: #6f8cde 1px solid;
OVERFLOW: auto;
BORDER-LEFT: #6f8cde 1px solid;
BORDER-BOTTOM: #6f8cde 1px solid;
HEIGHT: 200px;
}
One contained a list of child items, the other contained the parent items. I decided to create the items as SPAN tags, and using JavaScript, was created onclick events to toggle the selection and de-selection of items (SPAN tags).
function ToggleChildItemSpan() {
if (window.event.srcElement.className == "ChildItemHighlight") {
window.event.srcElement.className = "ChildItem";
}
else if (window.event.srcElement.className == "ChildItem") {
window.event.srcElement.className = "ChildItemHighlight";
}
}
Then, using DHTML, I copied over child item SPAN tags into the parent DIV area under the appropriate parent. As you can see in the illustration above, "Parent 1" contains "Child 1", "Child 2", "Child 3", and "Child 4." To make this work well, I needed each item to have custom attributes, such as ChildItemID and ParentItemID along with the SPAN tag ID to keep track of which items were where. Also, since an ASP.NET page can not detect tags created via DHTML, I had to create an XML data island to keep track of the associations. This is an IE only solution, but worked well for what I needed.
function DoAssociation() {
var SelectedParentItem = GetSelectedParentItem();
if(null == SelectedParentItem) {
alert("You must select a parent to relate selected children");
return;
}
var xmlDoc = document.all["xmlRelationships"].XMLDocument;
var SelectedChildItems = GetSelectedChildItems();
for(i = 0; i < SelectedChildItems.length; i++) {
SelectedChildItems[i].setAttribute("ParentItemID", SelectedParentItem.getAttribute("ParentItemID"));
var child_id = SelectedChildItems[i].getAttribute("ChildItemID")
var parent_id = SelectedParentItem.getAttribute("ParentItemID");
var node = xmlDoc.documentElement.selectSingleNode("//Relation[@ChildItemID='" + child_id + "' and @ParentItemID='" + parent_id + "']");
if(node == null) {
var root = xmlDoc.documentElement;
newNode = xmlDoc.createNode(1, "Relation", "");
newNode.setAttribute("ParentItemID", parent_id);
newNode.setAttribute("ChildItemSpanElementID", SelectedChildItems[i].uniqueID);
newNode.setAttribute("ChildItemID", child_id);
root.appendChild(newNode);
var divCopyOfChild = document.createElement("div");
divCopyOfChild.className = "AssociatedChildItem";
divCopyOfChild.setAttribute("runat", "server");
divCopyOfChild.setAttribute("ParentItemID", parent_id);
divCopyOfChild.setAttribute("ChildItemSpanElementID", SelectedChildItems[i].uniqueID);
divCopyOfChild.setAttribute("ChildItemID", child_id);
divCopyOfChild.onclick = ToggleAssociatedChildItemSpan;
divCopyOfChild.innerHTML = " " + SelectedChildItems[i].innerText + "
";
SelectedParentItem.parentElement.appendChild(divCopyOfChild);
SelectedChildItems[i].className = "ChildItem";
}
}
SelectedParentItem.className = "ParentItem";
}
Once I was ready to save the relationships, I wrote more JavaScript to put the XML in a hidden tag field that had the runat="server" attribute. That give me the XML on postback to save the relationship data. Everything runs on the client and is happy. There is one small tweak you have to make to get this to work, and that is to turn off validation in the @Page tag (specifically set validateRequest="false"). This attribute is set to true by default to prevent script posting, but since this is an intranet, IE-only solution, it's not an issue for me.
If you are interested in this code for your own tweaking, see the example page and use the View Source command.
Enjoy!