Friday, March 5, 2010

How to find the TabID of every ancestor of the current DNN (DotNetNuke) page

I needed to have a way of knowing the TabID of every ancestor page of an active DNN (DotNetNuke) page in a new skin that I was developing. The site that I am developing has a top-level menu bar that has about seven main sections in it. What was needed was a way to have the top-most link in the menu bar remain highlighted no matter how deep the active page that a user is browsing is underneath that section. I found some tutorials online that outlined ways to do this two or three levels down, but I really wanted to make something that would work at any level.


What I came up with was the following technique that makes use of VB.NET to recursively search through the ancestors (or parents) of the active Tab, and Javascript, JQuery and CSS to perform the client-side styling of the navigation elements.



Here is the HTML:


<div class="topNavBar">
<ul>
<li><a href="/?tabid=58" id="58">News & Events</a></li>
<li><a href="/?tabid=59" id="59">Ministries</a></li>
<li><a href="/?tabid=65" id="65">Missions</a></li>
<li><a href="/?tabid=66" id="66">Sports & Fitness</a></li>
<li><a href="/?tabid=67" id="67">Worship</a></li>
<li><a href="/?tabid=68" id="68">About Us</a></li>
</ul>
</div>

<input id="tabAncestors" name="tabAncestors" runat="server" type="hidden">

The site that is used in this example has six primary sections; links to these sections exist in a navigation bar that is visible on every page of our web site. The goal is to be able to control the CSS style of the navigation link that is the "ultimate ancestor" of any site page that is being browsed. For example; under the Ministries section we may have several different ministries, each of which may have further sub-sections. Let us imagine that we have the following path to a page that we are currently browsing (I have added the TabID of each page for clarity):

Ministries (59) > Adult Min (75) > Women's Min (76) > Bible Studies (84)

While we are on the "Bible Studies" page, or any of the other pages in this path, we want to always have the top-level navigation link ("Ministries" in this case) highlighted, so that the visitor has a visual cue as to which section of the site they are in.

In order to control the CSS style of the navigation links using the methods outlined here, we have given each link in the navigation bar an ID that is equal to the TabID of the top-level Tab in its section. We have also created a hidden INPUT control tabAncestors that will be used as a storage container for information that we will be using later in this tutorial. (Make sure to give the hidden INPUT control the runat="server" attribute so that we can read/write to it using VB.NET.)


Here is the VB.NET code:


Dim TabParentID As Integer
Dim TabAncestorList As New ArrayList()

Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs)
tabAncestors.Value = ""
GetTabAncestorIDs(PortalSettings.ActiveTab.TabID)
End Sub

Sub Page_PreRender(ByVal sender As Object, ByVal e As System.EventArgs)
For Each obj In TabAncestorList
tabAncestors.Value += obj.ToString + ","
Next obj
End Sub

Sub GetTabAncestorIDs(ByVal TabID As Integer)
If TabID > 0 Then
TabAncestorList.Add(TabID)
Dim tcTab As DotNetNuke.Entities.Tabs.TabController = New DotNetNuke.Entities.Tabs.TabController
TabParentID = tcTab.GetTab(TabID, PortalSettings.PortalId, True).ParentId
GetTabAncestorIDs(TabParentID) 'Recursive script.
End If
End Sub

During Page_Load we first make sure that the INPUT control tabAncestors is empty. We then run the GetTabAncestorIDs subroutine, sending it the value of the active Tab's TabID.

The GetTabAncestorIDs subroutine is a recursive script which takes a TabID as an argument. The first time that we run the GetTabAncestorIDs subroutine, we send it the TabID of the page that is currently active. If the TabID value is greater than zero, the subroutine continues by adding the value of TabID to the global Array TabAncestorList. Next, we use a DNN TabController (tcTab), to set the global variable TabParentID to the value of the ParentID of the Tab that is identified by the TabID variable. We then recursively run the subroutine again using this ParentID variable. The subroutine will continue to run recursively until the value that is sent as the TabID argument is -1 (which means that we have run out of Parent Tabs because we have reached the top-most ancestor of our currently active Tab).

This leaves us with two things:

  1. An empty tabAncestors INPUT control.
  2. A global Array TabAncestorList that contains, in order from bottom to top, the TabIDs of every ancestor of the currently active Tab. In the case given in the HTML the Array would contain the following values if we were on the "Bible Studies" page: {84,76,75,59}

Our next step is to make the values in the TabAncestorList Array accessible to our page via client-side scripting. To do this, we loop through the Array in Page_PreRender and add every value to the tabAncestors INPUT control, separated by a comma. This will allow the following Javascript and JQuery to use the information. In our example, the value of the tabAncestors INPUT control would be the string "84,76,75,59,".


Here is the Javascript code:


var HiddenFld = document.getElementById("<%= tabAncestors.ClientID %>");
var arr = HiddenFld.value.split(',');
for (x in arr) {
var idString = "#" + trim(arr[x]);
if (idString != "#null") {
$(idString).addClass('current');
};
};

//trim function taken from: http://www.go4expert.com/forums/showthread.php?t=2098
//This function simply removes spaces from the beginning and/or end of a string.
function trim(str) {
if (!str || typeof str != 'string')
return null;

return str.replace(/^[\s]+/, '').replace(/[\s]+$/, '').replace(/[\s]{2,}/, ' ');
};

Here we make a Javascript variable HiddenFld which is the INPUT control tabAncestors. We then split the value of HiddenFld into a Javascript Array arr using a comma as a delimiter. In our example this would give us ["84","76","75","59",""] (Note the empty/null value at the end because our string ended with a comma. We will deal with this null value in our script.). We then use a For loop to loop through the items in the arr Array to do the following:

  1. Create an idString variable which concatenates a pound sign (#) with the arr Array item (the TabID) that is currently being processed. This will result in a string looking something like: "#59" if the TabID in the Array is 59; or "#null" if the Array value is empty.
  2. Check to make sure the value of idString IS NOT "#null" before proceeding.
  3. Run a JQuery script that will add the class, "current" to any element in the DOM that has an ID matching idString.

In the case of the HTML in this example, if the "Ministries" Tab's TabID is "59" and we have given the "Ministries" ANCHOR tag in our main menu an ID of "59" then if we are currently browsing any Tab that is a child, grandchild, or great-great-great-great grandchild of the "Ministries" Tab, the ANCHOR tag will have the class, "current" added to it and we can affect the look of this ANCHOR tag through a CSS rule like this:

a#current
{
*/some special style info here/*
}


Using this method, we could even affect any sub-menu items that fall within the ancestor path of the current Tab, because they will also receive a class of "current" if they have been given an ID that matches the TabID that they link to.


No comments:

Post a Comment