建立一个类似YAHOO的导航条

This sample will demonstrate how to quickly build a simple Yahoo! style navigation bar that displays the paths a user has taken to get to a given directory, and also displays a table of links to the sub-directories of the current directory.

The first thing I like to do when beginning any database driven application is to design the data structure. Of course, once we get into writing the code, many times columns or tables need to be added or modified from the initial design, but having a data framework helps to give us some focus and direction.

Database Layout
I am using an Access2000 database for this example. You can definitely port this to any RDBMS easily; just follow the same table architecture.

Let's start by creating a new Access database, and name it Bread.mdb.
Create a single table and name it tblParents.
Open it up in Design Mode and add the following columns.


* You can skip building all this from scratch by downloading the source files.

Bread.mdb
Column Name Data Type
AID Auto Number (Primary Key)
DirName Text
ChildDir Text
ParentDir Text
ChildDirDescript Memo
DirDescript Memo

* If designing for SQL Server, make the AID Column an Identity Field, with an increment of 1 and make sure allow nulls is off.

Next we will want to populate our table with data, so let me explain the logic of the architecture.

Start by inserting the data for the root directory of your web server. This will be specified by inserting a forward slash / into the DirName column.
Next add the directory below the root to the ChildDir column (make sure you add the exact directory name as it appears on the server)
Since this is the root directory, leave the ParentDir column blank.
For the ChildDirDescript Column, you want to enter whatever you want the text to read for the link to the ChildDir directory that you just entered. (i.e., if you entered music as a ChildDir Name, you may want the link that points to music to also read Music, so you enter Music in the ChildDirDescript column for this entry. If you want it to read Modern Music, enter that)
For the DirDescript column, enter the text you want to appear to describe the current directory, which in this case is the root folder. I used the word Home as the description so that users will always know that clicking on this link will bring me back to the beginning.
Repeat this process for each sub directory you have below your web root.
Note: Make sure to create the actual directory structure on the web server if it does not already exist.
Your data should look similar to this:




After you complete this process make sure you also make entries in the database for the directories you just added. In my case I added a directory Music, so now I must add the appropriate Fields for Music. Since Music has a parent directory (Root), we must specify it by adding a forward slash for ParentDir.

Populate the data for Music and any other Sub Directories you like.

You may refer to the screenshot below for my example.




* The ChildDir columns should contain all the directories below the Root.

The Code
Now that we have some valid data, let's start by creating a couple of functions. We will house these functions in an include file, named folderFunctions.asp. The first function we want is a function to return the current physical directory of the web server. We will use this function when we do a database lookup to build the appropriate links and path history. I named this function GetThisPath.

First thing our function needs to do is get the current full path from the server. We will use the Path_Info method of the ServerVariables collection to look it up.

Since we only want the current directory name, we will split the string returned from Path_Info at the forward slash, which will create an array containing all the directories above the current directory. Then we will need to loop thru the entire array to pull only the data we need (the current directory). We will accomplish this by setting our loop to exit at the next to the last entry in the array (The very last entry would be the page name and we don't want that). The way we do this is to tell the loop to execute as many times as values the array holds minus one. We get that information by asking the array for it's upper Bounds.

Then we will set the functions return value to the last entry in our loop by overwriting the return value in each loop, which will be the current directory. However, if the array is empty, it means we are at the root of the server and we need to set the return value to a forward slash, since that is the value we specified for the root in our database. See the code below.


Function GetThisPath()
aPath = Request.ServerVariables("Path_Info")
pathArr = Split(aPath, "/")

For i = 1 to uBound(pathArr) - 1 'Offset page Name
'Loop thru path and return current directory only
GetThisPath = pathArr(i)
Next

IF GetThisPath = "" Then
GetThisPath = "/" 'We are at the Root level
End IF
End Function



The next function we will create is very similar to the last, with the exception that it returns an array of all the directories above the current one, not just a string containing the current directory name.


Function GetFullPath()
Dim arrFullPath()
aPath = Request.ServerVariables("Path_Info")
pathArr = Split(aPath, "/")

IF uBound(pathArr) > 0 Then
For i = 1 to uBound(pathArr) - 1 'Offset page Name
'Redimension the array size
Redim Preserve arrFullPath(i)
'add the value to the array
arrFullPath(i) = pathArr(i) & "/"
Next
Else
arrFullPath(0) = "/" 'Root Level
End IF

GetFullPath = arrFullPath
End Function



We can now begin building the sub routine that will build the actual bread crumb style list of paths followed to get to the current directory. The completed output will look like the screenshot below.




Bread crumbs screen shot

I named this sub BuildCrumbs, since it will be responsible for building the actual bread crumb links. Lets start by creating and opening a RecordSet that holds that data from our database. I am using the Jet OLEDB 4 provider. First we will define our connection string as strConnect.


strConnect = "Provider=Microsoft.Jet.OLEDB.4.0;Data Source=" _
& Server.MapPath("/data/bread.mdb")

Set oRS = Server.CreateObject("ADODB.Recordset")

strSQL = "SELECT Distinct DirName, ChildDirDescript , DirDescript " &
"FROM tblParents"
oRS.CursorLocation = adUseClient
oRS.Open strSQL, strConnect, 3, 3 'Open our RS



Next we will call the function GetFullPath that we created earlier and define 2 empty variables, strPath and strRootPath.


arrPaths = GetFullPath 'Call the function to Return an Array
strPath = ""
strRootPath = ""



Now we want to write some conditional logic. We need to build our structure differently depending on whether or not we are at the root level of the server. So we will place a call to the function GetThisPath to check if we are in fact at the root.


If GetThisPath <> "/" Then 'we are not at the Root Level



If we are not at the root of the server, then we will need to build the bread crumbs. We want our bread crumbs to link to the right directory but display the value of the appropriate DirDescript column to the user.

We can start by looping thru the array returned by our GetFullPath function.


For x = 0 To uBound(arrPaths)
strRootPath = strRootPath & arrPaths(x) 'Rebuild the Full Path
strPath = Replace(arrPaths(x), "/", "") 'Remove Slashes



We want to remove the slashes from the array values, so that we may compare the name to the name in our database.

Since we know that if we are inside this loop, the current user is not at the root level, we need to build a link back to the root. Remember this is inside a loop and we only want it to execute once, so we will create a conditional if statement for it.


IF strPath = "" Then 'This is the Root value
Response.Write "Home >> "
End IF



Now we need to nest a loop for our RecordSet inside of our For Loop. This is how we will compare the current server path to our database values and return the appropriate values to the user.


oRS.MoveFirst 'Start at the beginning of the Recordset
Do While Not oRS.EOF



We are now inside 2 loops. We need to compare the current value of the array (the parent loop), to the current record of the database (the child loop). We can do this by conditionally checking the values of each and comparing against each other. If they match, then we need to also check to make sure that the current recordset value is not the same as it was the last time the loop executed (since we have a one to many ratio of directories in our database), by setting a variable later on in the recordset loop equal to the current value of the recordsets DirName value. We will call this variable CurrDirName. On the next time thru the loop we don't want to do anything if CurrDirName is equal to the value of the recordset field. Since we know we are going to do this we can code for this check now.


'Compare the database value to the array
IF Trim(oRS("DirName")) = Trim(strPath) Then

'Dont build the link more than once
IF CurrDirName <> oRS("DirName") Then



We want to keep an eye out for the last value (or index) in our array, since it will be the current directory. The way we can check this is to see if our variable x has the same value as the upper bounds of the array arrPaths.


IF x < uBound(arrPaths) Then 'Not at the current directory
Response.Write "" & _
oRS("DirDescript") & " >> "
Else
'This is the current Directory
Response.Write "" & oRS("DirDescript") & ""
End IF



Now close the 3 conditonal checks we are performing, set the value of CurrDirName. And tell our loops to continue executing if they can.


End IF
End IF
End IF
currDirName = oRS("DirName")
oRS.MoveNext
Loop
Next



Now we are only inside one last conditional If statement depending on whether or not we are at the root of the server. Since if this code is executing we know for sure that we are not at the root level, we will still want to write something if and when we do get to the root. So let's go ahead and set up the logic to deal with the case where a user is at the root level. If they are at the root level, all we want to do is display a non-linked text Home message in the breadcrumbs.


Else
Response.Write "Home"
End IF

oRS.Close
Set oRS = Nothing



All that's left to do for this Sub is to close our recordsets and free the objects from memory.


oRS.Close
Set oRS = Nothing

End Sub



Only one more sub to go! This sub will handle building the list of sub-directories contained within the current directory. It will be used to build html to look like the screenshot below to your users. Let's name this sub PathFill.




Note: You could make this a function, but since it doesn't return a value I prefer making it a sub (where is the old void statement when you need it).

While we are at it, let's set up a few constants for ADO to access our recordsets with.


Const adUseClient = 3
Const adOpenStatic = 3
Const adLockOptimistic = 3

Sub PathFill()



We want to initialize an array and again open a recordset from our database fields, only this time we only want the data where the database column DirName matches the current directory. We will use the function GetThisPath to help us out with our SQL select statement.


Dim arrDirLinks() ' Initialize array
strConnect = "Provider=Microsoft.Jet.OLEDB.4.0;Data Source=" & _
Server.MapPath("/data/bread.mdb")

Set oRS = Server.CreateObject("ADODB.Recordset")
strSQL = "SELECT * FROM tblParents WHERE DirName = '" & _
GetThisPath & "'"
oRS.CursorLocation = adUseClient
oRS.Open strSQL, strConnect, 3, 3



Next we want to call our GetFullPath function and then loop thru it's returned array. Setting the variable strLinkPath equal to it's concatenated values. We will use the variable strLinkPath to build the link to the child or sub-directories contained in the current directory.


arrSQLPaths = GetFullPath
If GetThisPath <> "/" Then
For y = 0 To uBound(arrSQLPaths)
strLinkPath = strLinkPath & arrSQLPaths(y)
Next
End IF



Now we want to loop thru our recordset and add an index to our arrDirLinks array containing the actual html link to the child directories by parsing out the data from the recordset. We want to do this everytime as long as there is a child directory to pase and we are not at the root of the server. Our array should have the same amount of indexes as our recordset has, so we will make a variable (that I called x) keep track of the count.


oRS.MoveFirst
x = 0
Do While Not oRS.EOF
ReDim Preserve arrDirLinks(x)
IF oRS("ChildDir") <> "" OR oRS("DirName") = "/" Then
arrDirLinks(x) = " & strLinkPath & oRS("ChildDir") & Q & ">" _
& oRS("ChildDirDescript") & ""
End IF
oRS.MoveNext
x = x + 1
Loop



Finally, we want to close our recordsets, free up the memory and give our arrDirLinks array session scope. Then this sub is done.


oRS.Close
Set oRS = Nothing

'Store Link Array in Session For Later Retrieval
Session("arrDirLinks") = arrDirLinks
End Sub



Now add the initial html to the bottom of this include file (folderFunctions.asp) and save it in the includes folder.










Let's add another include file to this directory while we are here. We will call it topCrumbs.asp and it will display the bread crumb style link history to the user.











All that is left is to set up one last include file to display the list of sub-directories of the current directory. Name this include file buildChildLinks.asp.

Basically, all we want it to do is loop thru the arrDirLinks array and build the list of links. I also wanted to have the links built in a 2 columned table so I have some logic in there that checks whether the array holds an even amount of indexes or not and then formats the table of links appropriately. We will set a boolean variable named blnEven to either True or False depending on an even or odd return. We also want to set up the intial HTML for our links. Look Below.
















That's it! Now all we have to do is put all our include files together into a single page and place the page in all our directories.














Just some meaningless text here.

You would probably want to put your own stuff in here.

And not just ramble on like I am doing.

Well, See you all later.

Note: You may use either the whole application or you can break it up and just utilize the top bread crumbs functionality or the build child links functionality by only including the relevant includes in your page if you wish.