- XML Reference Guide
- Overview
- What Is XML?
- Informit Articles and Sample Chapters
- Books and e-Books
- Official Documentation
- Table of Contents
- The Document Object Model
- Informit Articles and Sample Chapters
- Books and e-Books
- Official Documentation
- DOM and Java
- Informit Articles and Sample Chapters
- Books and e-Books
- Implementations
- DOM and JavaScript
- Using a Repeater
- Repeaters and XML
- Repeater Resources
- DOM and .NET
- Informit Articles and Sample Chapters
- Books and e-Books
- Documentation and Downloads
- DOM and C++
- DOM and C++ Resources
- DOM and Perl
- DOM and Perl Resources
- DOM and PHP
- DOM and PHP Resources
- DOM Level 3
- DOM Level 3 Core
- DOM Level 3 Load and Save
- DOM Level 3 XPath
- DOM Level 3 Validation
- Informit Articles and Sample Chapters
- Books and e-Books
- Documentation and Implementations
- The Simple API for XML (SAX)
- Informit Articles and Sample Chapters
- Books and e-Books
- Official Documentation
- SAX and Java
- Informit Articles and Sample Chapters
- Books and e-Books
- SAX and .NET
- Informit Articles and Sample Chapters
- SAX and Perl
- SAX and Perl Resources
- SAX and PHP
- SAX and PHP Resources
- Validation
- Informit Articles and Sample Chapters
- Books and e-Books
- Official Documentation
- Document Type Definitions (DTDs)
- Informit Articles and Sample Chapters
- Books and e-Books
- Official Documentation
- XML Schemas
- Informit Articles and Sample Chapters
- Books and e-Books
- Official Documentation
- RELAX NG
- Informit Articles and Sample Chapters
- Books and e-Books
- Official Documentation
- Schematron
- Official Documentation and Implementations
- Validation in Applications
- Informit Articles and Sample Chapters
- Books and e-Books
- XSL Transformations (XSLT)
- Informit Articles and Sample Chapters
- Books and e-Books
- Official Documentation
- XSLT in Java
- Java in XSLT Resources
- XSLT and RSS in .NET
- XSLT and RSS in .NET Resources
- XSL-FO
- Informit Articles and Sample Chapters
- Books and e-Books
- Official Documentation
- XPath
- Informit Articles and Sample Chapters
- Books and e-Books
- Official Documentation
- XML Base
- Informit Articles and Sample Chapters
- Official Documentation
- XHTML
- Informit Articles and Sample Chapters
- Books and e-Books
- Official Documentation
- XHTML 2.0
- Documentation
- Cascading Style Sheets
- Informit Articles and Sample Chapters
- Books and e-Books
- Official Documentation
- XUL
- XUL References
- XML Events
- XML Events Resources
- XML Data Binding
- Informit Articles and Sample Chapters
- Books and e-Books
- Specifications
- Implementations
- XML and Databases
- Informit Articles and Sample Chapters
- Books and e-Books
- Online Resources
- Official Documentation
- SQL Server and FOR XML
- Informit Articles and Sample Chapters
- Books and e-Books
- Documentation and Implementations
- Service Oriented Architecture
- Web Services
- Informit Articles and Sample Chapters
- Books and e-Books
- Official Documentation
- Creating a Perl Web Service Client
- SOAP::Lite
- Amazon Web Services
- Creating the Movable Type Plug-in
- Perl, Amazon, and Movable Type Resources
- Apache Axis2
- REST
- REST Resources
- SOAP
- Informit Articles and Sample Chapters
- Books and e-Books
- Official Documentation
- SOAP and Java
- Informit Articles and Sample Chapters
- Books and e-Books
- Official Documentation
- WSDL
- Informit Articles and Sample Chapters
- Books and e-Books
- Official Documentation
- UDDI
- UDDI Resources
- XML-RPC
- XML-RPC in PHP
- Informit Articles and Sample Chapters
- Books and e-Books
- Official Documentation
- Ajax
- Asynchronous Javascript
- Client-side XSLT
- SAJAX and PHP
- Ajax Resources
- JSON
- Ruby on Rails
- Creating Objects
- Ruby Basics: Arrays and Other Sundry Bits
- Ruby Basics: Iterators and Persistence
- Starting on the Rails
- Rails and Databases
- Rails: Ajax and Partials
- Rails Resources
- Web Services Security
- Web Services Security Resources
- SAML
- Informit Articles and Sample Chapters
- Books and e-Books
- Specification and Implementation
- XML Digital Signatures
- XML Digital Signatures Resources
- XML Key Management Services
- Resources for XML Key Management Services
- Internationalization
- Resources
- Grid Computing
- Grid Resources
- Web Services Resource Framework
- Web Services Resource Framework Resources
- WS-Addressing
- WS-Addressing Resources
- WS-Notifications
- New Languages: XML in Use
- Informit Articles and Sample Chapters
- Books and e-Books
- Official Documentation
- Google Web Toolkit
- GWT Basic Interactivity
- Google Sitemaps
- Google Sitemaps Resources
- Accessibility
- Web Accessibility
- XML Accessibility
- Accessibility Resources
- The Semantic Web
- Defining a New Ontology
- OWL: Web Ontology Language
- Semantic Web Resources
- Google Base
- Microformats
- StructuredBlogging
- Live Clipboard
- WML
- XHTML-MP
- WML Resources
- Google Web Services
- Google Web Services API
- Google Web Services Resources
- The Yahoo! Web Services Interface
- Yahoo! Web Services and PHP
- Yahoo! Web Services Resources
- eBay REST API
- WordML
- WordML Part 2: Lists
- WordML Part 3: Tables
- WordML Resources
- DocBook
- Articles
- Books and e-Books
- Official Documentation and Implementations
- XML Query
- Informit Articles and Sample Chapters
- Books and e-Books
- Official Documentation
- XForms
- Informit Articles and Sample Chapters
- Books and e-Books
- Official Documentation
- Resource Description Framework (RDF)
- Informit Articles and Sample Chapters
- Books and e-Books
- Official Documentation
- Topic Maps
- Informit Articles and Sample Chapters
- Books and e-Books
- Official Documentation, Implementations, and Other Resources
- Rich Site Summary (RSS)
- Informit Articles and Sample Chapters
- Books and e-Books
- Official Documentation
- Simple Sharing Extensions (SSE)
- Atom
- Podcasting
- Podcasting Resources
- Scalable Vector Graphics (SVG)
- Informit Articles and Sample Chapters
- Books and e-Books
- Official Documentation
- OPML
- OPML Resources
- Summary
- Projects
- JavaScript TimeTracker: JSON and PHP
- The Javascript Timetracker
- Refactoring to Javascript Objects
- Creating the Yahoo! Widget
- Web Mashup
- Google Maps
- Indeed Mashup
- Mashup Part 3: Putting It All Together
- Additional Resources
- Frequently Asked Questions About XML
- What's XML, and why should I use it?
- What's a well-formed document?
- What's the difference between XML and HTML?
- What's the difference between HTML and XHTML?
- Can I use XML in a browser?
- Should I use elements or attributes for my document?
- What's a namespace?
- Where can I get an XML parser?
- What's the difference between a well-formed document and a valid document?
- What's a validating parser?
- Should I use DOM or SAX for my application?
- How can I stop a SAX parser before it has parsed the entire document?
- 2005 Predictions
- 2006 Predictions
- Nick's Book Picks
At this point, we have a working, static version of a hierarchical tree structure. The text is displayed based on whether a particular nodte is open or closed, and the appropriate "plus" or "minus" icon is displayed based on whether the node is open or closed. What we don't have is a way to make sure that the page is displayed with the appropriate node open to start with. Several ways exist to accomplish this. We could, if we choose, add a handler to the event that signifies that the page is loaded, and create JavaScript that would read the URL, detect the appropriate node, and display it in the open state.
In this case, however, we are going to use this opportunity to look at the way in which we can use .Net to display hierarchical data on a page. In a later entry, we'll do this with XML, but before we get to that point, let's look at the basic process.
.Net was designed from the beginning to make it easier to create dynamic web sites. One way in which it accomplishes this task
is to create "controls" to which you can bind a set of data. For example, you can easily create an HTML table of data
from a database by "binding" the data to a DataGrid
control. The control then inserts all of the appropriate
HTML, and even gives you the opportunity to specify styles are the table was a whole and of alternate rows.
But we need even more control than that. After all, we are not creating a table. If we follow the HTML at the end
of our last entry, in order to match the existing structure of the InformIT.com site, we will need to use lists instead.
Fortunately, .Net provides a control that gives us complete power over what it outputs. It's called a Repeater
.
Let's look at what we're actually going to do. We have a set of data, or rather two sets of data, that represent topics in the InformIT XML and Web Services Guide. One set represents the main topics, or "C heads", and the other represents the subtopics, or "D heads" that appear under them in the hierarchy. We can represent them in a database such as the one in figure 1:
Notice that we have a pair of columns, id
and parent_id
, that relate the rows.
For example, entry 12 is the parent for entries 14, 15, and 16. So we know that those items need to be displayed
as children of entry 12. We can use this data relation in displaying the page, even if it doesn't formally exist in the database.
To display these items on the page, we'll start with a basic Repeater
:
<%@ Page Language="VB" %> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/REC-html40/loose.dtd"> <html> <head> <base href="http://www.informit.com" /> </head> <body> <asp:Repeater id="CHeadRepeater" runat="server"> <HeaderTemplate> <h1>Other page content, such as the top of the page</h1> </HeaderTemplate> <ItemTemplate> <p><a href="/guides/content.asp?g=xml&seqNum=<%# DataBinder.Eval(Container.DataItem, "id") %>"> <%# DataBinder.Eval(Container.DataItem, "guideEntryTitle") %> </a></p> </ItemTemplate> <FooterTemplate> (The bottom of the page.) </FooterTemplate> </asp:Repeater> </body> </html>
Notice that the Repeater
control uses a prefix of asp:
. You're probably tempted
to think, "oh, I recognize that, that's an XML namespace alias." Well, not really, but it does serve much of the same purpose;
it tells the server that this is not a simple HTML element to display, and but rather an element to process.
The runat
attribute tells the server it's the one that should process it, and not the client.
As you might suspect, the HeaderTemplate
and FooterTemplate
elements
are processed before the first item, and after the last item, respectively. The ItemTemplate
element
gets processed for each item the Repeater
encounters, and that's where it gets interesting.
When the server encounters content in the <%# %>
notation, it knows that
it is supposed to process the contents based on the current row. For example, in this case we are executing
the DataBinder.Eval
function to get the id
and guideEntryTitle
attributes of the object representing the current item. We can add any function we want into one of these
evaluation blocks, as we'll see shortly.
Now let's populate the Repeater
:
<%@ Page Language="VB" %> <%@ Import Namespace="System.Data" %> <%@ Import Namespace="System.Data.Odbc" %> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/REC-html40/loose.dtd"> <script language="VB" runat="server"> Sub Page_Load(Src As Object, E As EventArgs) Dim MyConnection As OdbcConnection Dim MyCommand As OdbcCommand Dim ConnectionString As String Dim ds As DataSet ConnectionString = _ "Driver={Microsoft Access Driver (*.mdb)};" & _ "Dbq="&Server.MapPath("guideentries.mdb")&";" myConnection = new Odbc.OdbcConnection(ConnectionString) Dim CHeads As OdbcDataAdapter = _ New OdbcDataAdapter( _ "select * from GuideEntries where parent_id=0 order by listOrder", _ myConnection ) ds = New DataSet() CHeads.Fill(ds, "cheads") CHeadRepeater.DataSource = ds.Tables("cheads") CHeadRepeater.DataBind() myConnection.Close() End Sub </script> <html> <head> <base href="http://www.informit.com" /> </head> <body> <asp:Repeater id="CHeadRepeater" runat="server"> <HeaderTemplate> ...
First, note that the server executes the Page_Load
subroutine on, as you might have guessed, loading the page.
To that end, we can use this opportunity to create a set of data and bind it to the Repeater
.
In my case, I'm running my ASP.net pages in a hosted environment in which I can only access an Access
database, and only directly, so my first step is to create an ODBC connection. (If you are using, say,
SQL Server, you'll load the System.Data.SqlClient
classes instead.) Once we've got the connection,
we create a DataAdapter
that uses the connection to select all of the main topics, and use it
to create a table in the DataSet
. (We can tell they are main topics because they have a parent_ID
of 0
.)
It's important to note that we didn't just "populate" the DataSet
, as we might have done with a
RecordSet
. One of the advantages of using a
DataSet
is that it can hold more than one table of data. In this case, we've created one arbitrarily called
cheads
. In a few moments, will create a second one and related to the first.
In the meantime, we are using this table as the data source for the Repeater
, which we can identify
from the id
attribute we gave it in the body of the page. Once we assign it a data source,
we bind the data to it, which gives us a page like figure 2:
Now we can go ahead and create the data relation so we can display the subtopics:
... Dim CHeads As OdbcDataAdapter = _ New OdbcDataAdapter( _ "select * from GuideEntries where parent_id=0 order by listOrder", _ myConnection ) ds = New DataSet() CHeads.Fill(ds, "cheads") Dim DHeads As OdbcDataAdapter = _ New OdbcDataAdapter( _ "select * from GuideEntries where parent_id > 0 order by listOrder", _ myConnection ) DHeads.Fill(ds, "dheads") ds.Relations.Add("topicRelation", _ ds.Tables("cheads").Columns("id"), _ ds.Tables("dheads").Columns("parent_id")) CHeadRepeater.DataSource = ds.Tables("cheads") CHeadRepeater.DataBind() myConnection.Close() End Sub </script> ...
Here we have a second data adapter that adds a second table of data, called "dheads", to the
DataSet
. We can then create the parent-child relationship,
topicRelation
, by relating to the parent_id
column in the
child table to the id
column in the parent table.
It's important to note that this case, in which both sides of the relation represent the same table in the database, is actually a rarity. In most cases, you will use this capability to draw a relations between, say, a customer table and an order table, or even different types of data.
Now that we've got the relation, we can actually nest another Repeater
within the first:
... <asp:Repeater id="CHeadRepeater" runat="server"> <HeaderTemplate> <h1>Other page content, such as the top of the page</h1> </HeaderTemplate> <ItemTemplate> <p><a href="/guides/content.asp?g=xml&seqNum=<%# DataBinder.Eval(Container.DataItem, "id") %>"> <%# DataBinder.Eval(Container.DataItem, "guideEntryTitle") %> </a></p> <asp:Repeater id="DHeadRepeater" runat="server" datasource='<%# Container.DataItem.Row.GetChildRows("topicRelation") %>'> <HeaderTemplate> <div style="border: 1px dashed black"> </HeaderTemplate> <ItemTemplate> <%# Container.DataItem("guideEntryTitle") %><br /> </itemtemplate> <FooterTemplate> </div> </FooterTemplate> </asp:Repeater> </ItemTemplate> <FooterTemplate> (The bottom of the page.) </FooterTemplate> </asp:Repeater> ...
Notice that the new Repeater
is within the ItemTemplate
of the first one. That means it will be executed once for each row of the original set of data. But what will it use for data? Well, notice the datasource
attribute.
Remember, when the server encounters that type of evaluation block, it knows it has to
calculate the contents based on the current row. Because we have created a data relation,
the server can pull all of the child rows for the current row. The results look something like
figure 3:
Now that we have the basic Repeater
s working, we can insert the actual
HTML we want to output:
<html> <head> <base href="http://www.informit.com" /> <script type="text/javascript"> function toggle(plusminus){ if (plusminus.src=="http://www.informit.com/display/common/images/icons/plus.gif"){ plusminus.src="/display/common/images/icons/minus.gif"; } else { plusminus.src="/display/common/images/icons/plus.gif"; } base = plusminus.parentNode; allChildren = base.getElementsByTagName("ul")[0].getElementsByTagName("li"); for (i = 0; i < allChildren.length; i++){ el = allChildren[i]; if (el.style.display!="block"){ el.style.display="block"; } else { el.style.display="none"; } } } </script> <style type="text/css"> ul { display: block; list-style-type: none; } </style> </head> <body> <asp:Repeater id="CHeadRepeater" runat="server"> <HeaderTemplate> <h2>Other page content, such as the top of the page</h2> <ul> </HeaderTemplate> <ItemTemplate> <li class="chead"> <img src="/display/common/images/icons/plus.gif" alt="Expand" onclick="toggle(this)" /> <span class="cheadT"> <a href="/guides/content.asp?g=xml&seqNum=<%# DataBinder.Eval(Container.DataItem, "id") %>"> <%# DataBinder.Eval(Container.DataItem, "guideEntryTitle") %> </a> </span> <ul style="display: block;"> <asp:Repeater id="DHeadRepeater" runat="server" datasource='<%# Container.DataItem.Row.GetChildRows("topicRelation") %>'> <ItemTemplate> <li class="dhead" style="display: block"> <img src="/display/common/images/icons/plus.gif" alt="Expand" onclick="toggle(this)" /> <a href="/guides/content.asp?g=xml&seqNum=<%# Container.DataItem("id") %>"> <%# Container.DataItem("guideEntryTitle") %> </a> </li> </itemtemplate> </asp:Repeater> </ul> </li> </ItemTemplate> <FooterTemplate> </ul> (The bottom of the page.) </FooterTemplate> </asp:Repeater> </body> </html>
Now the page is virtually identical to the one we created earlier. (There is a
slight change to the toggle
script based on the fact that we will be manipulating the li
elements directly.) As before, the JavaScript
gets executed by the browser, not the server. But it is the server that creates the
HTML, including the links.
The result looks like figure 4:
At this point, we are essentially right back where we started. We have a page that displays our information, and opens and closes the appropriate node when requested. But we still need to have the server display nodes in their proper state to start with. We'll start by determining which main topic should be open:
<%@ Page Language="VB" %> <%@ Import Namespace="System.Data" %> <%@ Import Namespace="System.Data.Odbc" %> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/REC-html40/loose.dtd"> <script language="VB" runat="server"> Dim openId as String Dim ds As DataSet function getParentId(childId as String) Dim targetRows As Array targetRows = ds.Tables("dheads").select("id="&childId) if (targetRows.Length = 0) return 0 else Dim thisRow As DataRow = targetRows.GetValue(0) return thisRow("parent_id") end if end function Sub Page_Load(Src As Object, E As EventArgs) Dim MyConnection As OdbcConnection Dim MyCommand As OdbcCommand Dim ConnectionString As String ConnectionString = _ "Driver={Microsoft Access Driver (*.mdb)};" & _ "Dbq="&Server.MapPath("guideentries.mdb")&";" myConnection = new Odbc.OdbcConnection(ConnectionString) Dim CHeads As OdbcDataAdapter = _ New OdbcDataAdapter( _ "select * from GuideEntries where parent_id=0 order by listOrder", _ myConnection ) ds = New DataSet() CHeads.Fill(ds, "cheads") Dim DHeads As OdbcDataAdapter = _ New OdbcDataAdapter( _ "select * from GuideEntries where parent_id > 0 order by listOrder", _ myConnection ) DHeads.Fill(ds, "dheads") ds.Relations.Add("topicRelation", _ ds.Tables("cheads").Columns("id"), _ ds.Tables("dheads").Columns("parent_id")) Dim selectedId As String = Request.QueryString("seqNum")&" " if (selectedId = " ") selectedId = 0 openId = 0 else Dim parentId As String = getParentId(selectedId) if (parentId = 0) openId = trim(selectedId) else openId = parentId end if end if CHeadRepeater.DataSource = ds.Tables("cheads") CHeadRepeater.DataBind() myConnection.Close() End Sub </script> ...
A couple of things to notice here. First, we have declared two global variables, which will be needed by
other functions. One, the DataSet
, we've moved from inside the Page_Load
subroutine so that we can use it in the getParentId
function, which accepts an
id
value, and uses it to to locate a specific row in the dheads
table.
If the record exists in that table, it returns the value of the parent_id
column. If not,
it is assumed to be a main topic, which has a parent_id
of 0
.
We can then use that function to determine which main node should be open. If the URL for the request
contains a main topic ID, then of course that is the topic that should be open. If the URL contains the id of a
sub topic, we'll want to use its parent. The actual openId
variable is global so we can
use it to set the style for each subtopic:
... return thisRow("parent_id") end if end function function itemStyle(thisId as String) Dim style As String if (thisId = openId or getParentId(thisId) = openId) style = "style='display: block'" else style = "style='display: none'" end if return style end function Sub Page_Load(Src As Object, E As EventArgs) ... <asp:Repeater id="DHeadRepeater" runat="server" datasource='<%# Container.DataItem.Row.GetChildRows("topicRelation") %>'> <ItemTemplate> <li class="dhead" <%# itemStyle(Container.DataItem("id")) %>> <img src="/display/common/images/icons/plus.gif" alt="Expand" onclick="toggle(this)" /> <a href="/guides/content.asp?g=xml&seqNum=<%# Container.DataItem("id") %>"> <%# Container.DataItem("guideEntryTitle") %> </a> </li> </itemtemplate> ...
The itemStyle
function simply compares the current id (or rather, its parent id) with the
openId
and provides the appropriate style, which we add to the element using the evaluation block.
If you now display the page without specifying a node to open, it should open with all nodes closed:
If you then display the page with a selected node, as in:
http://farmernick.somee.com/guide4.aspx?seqNum=12
you can see that node 12, "DOM and Java", is open. But what about we want one of the subtopics? For example, topic 29 is a sub topic of "DOM Level 3". So the URL:
http://farmernick.somee.com/guide4.aspx?seqNum=29
gives us a page like:
because the server first determines the parent node and opens that one. Now all that's left is to make sure that the open node gets the appropriate icon:
... return thisRow("parent_id") end if end function function getPlusMinus(thisId As String) if (thisId = openId) return "minus.gif" else return "plus.gif" end if end function function itemStyle(thisId as String) Dim style As String ... <ItemTemplate> <li class="chead"> <img src="/display/common/images/icons/<%# getPlusMinus(Container.DataItem("id"))%>" alt="Expand" onclick="toggle(this)" /> <span class="cheadT"> ...
Subtopics will never be displayed as open to start with, so they always get the plus sign, but in this case the open main topic does get the minus sign.
So now we have a working tree navigational structure, which displays on the page with the appropriate node open, and enables users to open and close other nodes at will.