Creating a Tabbed Interface for Displaying Parent/Child Data
By Scott Mitchell
Introduction
Last week I wrote an article titled Creating Collapsible Detail Regions in a Repeater, which looked at how to use a Repeater and some client-side JavaScript and HTML markup to help manage information overload. (Be sure to check out the live demo of the collapsible regions Repeater!) After writing last week's article, I got to thinking about how this concept could be enhanced to provide a "prettier" user interface. While I am the first to admit that I am anything but graphically and artistically inclined, I think my efforts to date on prettying up the interface are worth sharing. (See for yourself at the live demo.)
My efforts resulted in creating a tabbed interface for displaying parent/child data (although it could be used just like in the collapsible regions Repeater example, showing details for a particular item). The user interface has the parent items listed in a series of vertically arranged tabs on the left, with the details displayed on the right. Clicking a tab "selects" the tab, and displays its details in the pane to the right.
The end goal of this is to create a custom ASP.NET server control that will allow one to create a tabbed interface by simply dropping a Web control from the Toolbox in Visual Studio .NET onto the Designer. That project, though, will likely take some time, so in the interim I thought it would be worthwhile to share the Repeater and client-side script and markup. My reasoning for this is two-fold: first, it will hopefully be useful for some folks out there, who can incorporate this look and feel into their site; second, the client-side script and markup could use some work. It looks great in Internet Explorer, but, while functional, is not nearly as pleasant in FireFox. (I've yet to test it with browsers other than IE or FireFox.) My hope is that someone with more CSS and DHTML experience than I can provide some pointers on improving things.
Laying Out the Tabs
Before continuing, be sure to check out the live demo so that you are familiar with the look and feel of the output.
When crafting the client-side markup to generate the tabbed interface, I wanted to have the markup generated so that parent and child information followed one another in the generated HTML. This is because my assumption was that the Repeater control being used would be grabbing the parent and child data together, similar to how the abbreviated and detailed information was accessed in the collapsible regions Repeater article from last week. That is, I expected to have a Repeater with a declarative syntax like so:
|
With such a Repeater, the HTML rendered on the page would look something like:
... markup for parent (or abbreviated) information for item 1...
|
The challenge was, given this markup, how to layout the elements of the page in the needed fashion. By setting the
display
CSS property of the child (or detailed) information markup to none
, I could effectively
hide the detail information, meaning I'd be given a series of parent (or abbreviated) titles, which is what I was after.
The challenge was in having the child (or detailed) information, when displayed, displayed so that its upper-left hand
corner was flush with the top-most parent tab's upper-right hand corner.
The only approach I could see that would work would be to use absolute positioning.
All of the markup is encased within a containing <div>
tag.
In order to layout the tabs and child/details data correctly, I created a client-side JavaScript function, setInitialPositions()
,
that is called after the page has completely loaded. This function sets the absolute positions of the tabs and child/details panes,
using the following formulas:
Details Top Coordinates = Calculated Top coordinates of the containingIf the containing<div>
Details Left Coordinates = Calculated Left coordinates of the containing<div>
+ the width of the tabs + 1
<div>
does not have an explicitly set height, the setInitialPositions()
also determines the height of the maximum detail item, and assigns this value to the height of the containing <div>
.
(This causes all child/detail items, regardless of how much information they contain, to have the height of the tallest child/details item.)
To determine the top and left coordinates of the containing <div>
I use two recursive functions,
getAscendingTops(element)
and getAscendingLefts(element)
, which walk up the passed-in
element's set of containing HTML element, summing up their top and left offsets. A more thorough discussion of
this technique can be found at Determining the Location of a Nonpositioned
Element, and is the technique utilized by my free, open-source ASP.NET menu control, skmMenu.
Hiding and Showing Child Data When a Parent Tab is Clicked
When a parent tab is clicked the associated child/details data needs to be displayed. This is accomplished by adding a client-side
onclick
event handler to each tab that calls the JavaScript function showTopic(child/details index)
.
The child/details index is the index of the child/details data. The first parent tab's child/details data has an index of 0,
the second 1, and so on. The showTopic()
function performs the following tasks:
- Hides the currently selected child/details information (by setting the
display
property tonone
). - Resets the currently selected tab so that is appears "unselected."
- Shows the child/details information corresponding to the tab that was clicked (by setting the
display
property toinline
). - Makes the clicked tab appear "selected," by changing its background color and right border
Displaying Database Data in the Tabbed Interface
In order to display parent/child or abbreviated/detailed database information in the tabbed interface you will need to do the following:
- Create an ASP.NET page with the requisite client-side JavaScript functions,
- Add a Repeater with the appropriately marked up contents within its templates, and
- Add the server-side code that binds the appropriate database data to the Repeater.
<div>
elements
per item - one that contains the parent or abbreviated information, and the other containing the child or detail information
for that particular parent/abbreviated data. Earlier I mentioned that the entire tabbed interface markup is encased within
a containing <div>
. This can be defined around the Repeater's declarative syntax, or injected within the
Repeater's HeaderTemplate and FooterTemplates. In a nutshell, the Repeater templates and markup will look like:
|
In the example above, the containing <div>
is placed within the HeaderTemplate and FooterTemplate.
Note that its height
is set to 350px. This will cause the tabbed interface to have a height of precisely
350px regardless of how much space the child/details data requires. Child/detail information that exceeds 350px will contain
a vertical scrollbar. If you omit this explicit height
setting in the containing <div>
the height of the tabbed interface will be set to the height needed by the tallest child/details data.
In the ItemTemplate there are two <div>
elements. The first one, whose id
equals hIndex
,
where Index is the index of the current RepeaterItem, contains the parent or abbreviated information. Whatever contents you
place within this first <div>
will be what appears in the corresponding tab. The second <div>
in the ItemTemplate contains the child/details data; it has an id
of dIndex
. (These id
s
are important to note, as they are used in the client-side functions to programmatically reference and alter the styles of
the HTML elements.)
Both <div>
elements in the ItemTemplate contain styles that define their background colors, border behavior,
padding, overflow behavior, and so on. You can tweak some of these settings, such as background-color
, to customize
the tab interface for your site. All of the work of explicitly positioning the detail items, and altering the styles based
on what tab is clicked, is handled by JavaScript functions, which can be seen (and copied from) the live demo.
One last comment - it is important that, at the bottom of your page, you call the setInitialPositions()
function.
Also, you can opt to have the first tab automatically displayed by calling showTopic(0)
. I accomplish this
by adding the following <script>
block after the Repeater:
|
Conclusion
In this article we looked at how to implement a tabbed interface that can show parent/child or abbreviated/detailed information. This was accomplished by using some client-side JavaScript along with a Repeater control to render the data in appropriately formatted markup. The end goal of this project is to create a custom server control that can provide this output with the ease of dragging and dropping a control onto an ASP.NET Web page. For the time being, however, to implement this on your site you'll need to visit the live demo and copy and paste the code in its entirety (although there are a number of places you can use to customize the appearance of the tabbed interface).
Happy Programming!
Attachments: