Hello guys, I wonder if you can help. I've got an existing site that I want to "convert" to an ASP.NET one and I gave a good read at the master template tutorial suggested in another thread, but I'm having a few problems deciding what goes in the master page and what doesn't. On the general level it is obvious of course, the elements common to all the pages go in the master page, the rest is out. In my case I've got the following problem: the navigation on the home page has the following markup:

<div class="navigation">
    <a href="home.html" class="active">Home</a>
    <a href="about.html">About</a>
    <a href="other_projects.html">Other projects</a>
    <div class="clear"></div>             
</div>

In my asp.net master page though, I decided not to include the navigation elements:

<div class="navigation">
    <asp:ContentPlaceHolder ID="navigationPlaceholder" runat="server">
    </asp:ContentPlaceHolder>
    <div class="clear"></div> 
</div>

and to leave that to each page.Now, you could argue that the navigation is a common element across the site, and in fact it is, but the markup will be slightly different for each page: we saw the home page above, here are the two other pages. About page:

<div class="navigation">
    <a href="home.html">Home</a>
    <a href="about.html" class="active">About</a>
    <a href="other_projects.html">Other projects</a>
    <div class="clear"></div>             
</div>

Other project page:

<div class="navigation">
                <a href="home.html">Home</a>
                <a href="about.html">About</a>
                <a href="other_projects.html" class="active">Other projects</a>
                <div class="clear"></div>             
            </div>

As you can see the difference is the active class and where it sits on each page, that's why I did it this way. Is it right, wrong? I mean if I had to include the full navigation in the master page, how do I then place the class in the right place on the right page?

Recommended Answers

All 15 Replies

So what i would recommend is that you do use the master page, but think about another way to modify the whats in the navigation menu depending on what page you are on. You have two options.. do this server side or client side.

If you do this server side, you'd have to access teh controls in the master page from your content pages. While this can be done, and there are a lot of use cases where you would do this, i wouldnt use this approach for your particular scenario. I'd do this client side using javascript/jquery to add/remove the class attribute depending on what page you are on.

Ok so there are various was to do this, but an easy way that i may investigate comes to mind. Here is what I would do...

on your master page, add a hidden control somewhere on the page, towards the bottom is fine, but within your form elements. Say your hidden control's ID is "hdnPageName". Your control would look like this on the master page...

<asp:HiddenField ID="hdnPageName" ClientIDMode="Static" runat="server" />

You include the ClientIDMode property because we need the ID of this element to not be changed by the asp.net rendering process. I can explain this seperately if needed.

On the master page's code behind within the page load subroutine (assuming C#)...

hndPageName.Value = System.IO.Path.GetFileNameWithoutExtension(Request.ServerVariables["SCRIPT_NAME"]);

Ok, so once you have this in place, no matter what page you load in your browser, look at the source code. Search for an input element with an ID = "hdnPageName". It should have a value of the name of page.

Ok, so now to the client side js/jQuery. I like jQuery so here is an example..

assuming this...

<div class="navigation">
    <a id="home" href="home.html" class="navItem" >Home</a>
    <a id="about" href="about.html" class="navItem" >About</a>
    <a id="other_projects" href="other_projects.html" class="navItem" >Other projects</a>
    <div class="clear"></div>             
</div>

Add this script towards the bottom of your master page.

<script>
    var pageName = $('#hdnPageName').val();
    $('.navItem').removeClass('active');
    $('#' + pageName).addClass('active');
</script>

None of this code is tested, but should get you close to what you are looking for. please keep in mind that I'm not a developer by trade, so the stuff i come up with is from things that I pick up on my own and have been tinkering with. There are definately different ways to handle this... i suspect that there may be other methods that would be better to implement.

how do I then place the class in the right place on the right page?

If you use <asp:HyperLink ID="HomeLink" runat="server">Home</asp:HyperLink> instead of a, then you can add HomeLink.CssClass = "active"; server-side where needed.

thanks for that. OK a few things.I looked up the code you used but I'm not sure I understand the whole thing.
ClientIDMode="Static" not sure what it does, other than returning an ID presumably
hndPageName.Value = System.IO.Path.GetFileNameWithoutExtension(Request.ServerVariables["SCRIPT_NAME"]); with this, do I need to replace SCRIPT_NAME with what? So basically this method is supposed to return the file name of whatever string you pass as an argument - from what I read - so what's that Request.ServerVariables["SCRIPT_NAME"] doing? What is it getting?
I have implemented the first part:
<asp:HiddenField ID="hdnnPageName" ClientIDMode="Static" runat="server /"> at the bottom of the Site.master page and then in the code behind of that page I have

protected void Page_Load(object sender, EventArgs e){
    hdnPageName.Value = System.IO.Path.GetFileNameWithoutExtension(Request.ServerVariables["SCRIPT_NAME"]);
}

and added the IO name space (not sure if this was needed or not) using System.IO;
but when I run it I get an error:
Type 'System.Web.UI.WebControls.HiddenField' doesn't have a public property named 'ClientIDMode'

In general though, with your code you're essentially trying to generate an id whose value will be the page name without the aspx extension, correct, or have I got things wrong?
$('#' + pageName).addClass('active'); this line in jquery: say we are on the home page (which by the way will be Home.aspx and not home.html, sorry my mistake) what value would pageName assume? I take "Home"? But this $('#' + pageName).addClass('active'); looks for an element with an ID of Home and adds a class of active to it? Sorry problably I completely misunderstood just for a change, : - )

see comments...

ClientIDMode="Static" not sure what it does, other than returning an ID presumably

ASP.NET handles the assignment of client side IDs to your elements when they are rendered in HTML. Just because you assign a control an ID, say for a Label, "label1", that doesnt mean that when the label control is rendered for HTML...that the <span> (this is a label control) will have the id="label1". If you want to make sure that this label-->span element has an ID="label1", you have to tell asp.net to be static for its ClientIDMode on the control. If you wanted this behavior for all of your controls, you can apply this in the web.config file. The client ID isnt important for you until to try to work with these elements client side. think about how javascript and jQuery work. You need to know the element's ID to be able to work directly with it.

hndPageName.Value = System.IO.Path.GetFileNameWithoutExtension(Request.ServerVariables["SCRIPT_NAME"]); with this, do I need to replace SCRIPT_NAME with what? So basically this method is supposed to return the file name of whatever string you pass as an argument - from what I read - so what's that Request.ServerVariables["SCRIPT_NAME"] doing? What is it getting?

You use it as i wrote it. asp.net will get the SCRIPT_NAME server variable and assign that value to the hidden element. in essencse what you get is the name of the aspx page without the file extension.

and added the IO name space (not sure if this was needed or not) using System.IO;

Your are basically telling asp.net where to get access to this method because you are using a method here..GetFileNameWithoutExtension().

In general though, with your code you're essentially trying to generate an id whose value will be the page name without the aspx extension, correct, or have I got things wrong?

yes.

$('#' + pageName).addClass('active'); this line in jquery: say we are on the home page (which by the way will be Home.aspx and not home.html, sorry my mistake)

I would expect it to be home.aspx because this is an asp.net web application. it wouldnt makes sense to have some pages that are .aspx and other .html.

what value would pageName assume? I take "Home"? But this $('#' + pageName).addClass('active'); looks for an element with an ID of Home and adds a class of active to it? Sorry problably I completely misunderstood just for a change, : - )

yes.

Maybe i've complicated this for you....see pritaeas response. even if you do this server side, you would do this from the master page's code behind in the page load method and you still have to figure out what page you are on so you can apply the class server side. Or you can try to do this from the individual pages, but then you have to access the controls that are on the master page. problem is if you do this on every page, and you change your mind later, you have to go back to each page...that's why i would recommend sticking with a master page design.

If you use <asp:HyperLink ID="HomeLink" runat="server">Home</asp:HyperLink> instead of a, then you can add HomeLink.CssClass = "active"; server-side where needed.

Or you can try to do this from the individual pages, but then you have to access the controls that are on the master page.

I'd add a public method that adds the class, instead of accessing the controls.

"I'd add a public method that adds the class, instead of accessing the controls." (sorry didn't let me quote for some strange reasons)

Thanks, but I presume that even if I add a public method I still need to somewhow figure out which page to add the active class to, as JorgeM has pointed out.

I mean I like the other approach as it seems less complicated but only half way implementing that I get an error as I metioned in my previous post:
Type 'System.Web.UI.WebControls.HiddenField' doesn't have a public property named 'ClientIDMode'
and
Validation (ASP.NET):Attribute 'ClientIDMode'is not a valid attribute of element 'HiddenField'
That's without having added the clientside js. ANy idea why I'm getting that?

Thanks, but I presume that even if I add a public method I still need to somewhow figure out which page to add the active class to, as JorgeM has pointed out.

Yes, but you call that method from the page in question, so you can use a parameter to identify that page.

OK so, let's see if I got this right:
- replace my anchor links with:

<asp:HyperLink ID="HomeLink" runat="server">Home</asp:HyperLink>
<asp:HyperLink ID="AboutLink" runat="server">About</asp:HyperLink>
<asp:HyperLink ID="OPLink" runat="server">Other projects</asp:HyperLink>

in the master page (where do I put the target page within an asp link by the way?)
-in the master page aspx.cs create a function that adds the class active to the navigation, something like:

public void ActiveClass(string whatPage){
    string PageName = whatPage;
    switch(PageName){

        case "Home"
        HomeLink.CssClass = "active";
        break;

        case "AboutLink"
        AboutLink.CssClass = "active";
        break;

        case "OPLink"
        OPLink.CssClass = "active";
        break;
    }
}

-in the pages in question call the function defined in the master page like so:

From the Home.aspx:
ActiveClass("Home");
From the About.aspx
ActiveClass("AboutLink");
From the Other_projects.aspx
ActiveClass("OPLink");

Exactly.

where do I put the target page within an asp link by the way?

Target="About.aspx"

OK, thanks, let me have a go then and see if it works :-). Thanks, I will post back

OK, I tried that but it wasn't successful as I'm getting an error now. This is what I have done. In the Site.master.aspx.cs I have this code:

using System;
using System.Collections;
using System.Configuration;
using System.Data;
using System.Linq;
using System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using System.Web.UI.HtmlControls;
using System.Xml.Linq;
using System.IO;



public partial class Site : System.Web.UI.MasterPage
{

  public void ActiveClass(string whatPage) {
        string PageName = whatPage;
        switch (PageName) {
        case "HomeLink":
        HomeLink.CssClass = "active";
        break;

        case "AboutLink":
        AboutLink.CssClass = "active";
        break;

        case "OPLink":
        OPLink.CssClass = "active";
        break;

        }
    }
}

In my content pages I call the function in this fashion:
Home.aspx.cs:

public partial class Home : System.Web.UI.Page
{
    protected void Page_Load(object sender, EventArgs e)
    {
        Site master = new Site();
        master.ActiveClass("HomeLink");
    }
}

About.aspx.cs

...
public partial class About : System.Web.UI.Page
{
    protected void Page_Load(object sender, EventArgs e)
    {
        Site master = new Site();
        master.ActiveClass("AboutLink");
    }
}

Other_projects.aspx.cs

...
public partial class Other_projects : System.Web.UI.Page
{
    protected void Page_Load(object sender, EventArgs e)
    {
        Site master = new Site();
        master.ActiveClass("OPLink");
    }
}

When I run the application, I don't get any error in the compiler but the webpage returns one of those Server error:

Object reference not set to an instance of an object.
Description: An unhandled exception occurred during the execution of the current web request. Please review the stack trace for more information about the error and where it originated in the code.

Exception Details: System.NullReferenceException: Object reference not set to an instance of an object.

And here is the stack trace:

[NullReferenceException: Object reference not set to an instance of an object.]
   Site.ActiveClass(String whatPage) +94
   About.Page_Load(Object sender, EventArgs e) +37
   System.Web.Util.CalliHelper.EventArgFunctionCaller(IntPtr fp, Object o, Object t, EventArgs e) +14
   System.Web.Util.CalliEventHandlerDelegateProxy.Callback(Object sender, EventArgs e) +35
   System.Web.UI.Control.OnLoad(EventArgs e) +99
   System.Web.UI.Control.LoadRecursive() +50
   System.Web.UI.Page.ProcessRequestMain(Boolean includeStagesBeforeAsyncPoint, Boolean includeStagesAfterAsyncPoint) +627

Not sure what I am doing wrong...any idea?

The master already exists in the pages (property Master). You need to do:

if (Master is Site)
    (Master as Site).ActiveClass("OPLink");

instead of creating a new master page.

OK that's working, thanks pritaeas. I have some questions though, lol, sorry!
With the code you posted above you are essentially testing whether a master page exists already (and we know it does because it's Site.master), and if there is, that (Master as Site) (which presumably is a 'synonym' of the master page) is used to call the function in the master page. Now, I didn't see this stuff in my master page tutorial, do you know any resource where I can read about this Master property and how it is used (that's because this syntax is a bit new to me, I mean the if statement doesn't even have curly brackets!!??)

Also, one more thing: in the main function

public void ActiveClass(string whatPage) {
        string PageName = whatPage;
        switch (PageName) {
        case "HomeLink":
        HomeLink.CssClass = "active";
        break;
        case "AboutLink":
        AboutLink.CssClass = "active";
        break;
        case "OPLink":
        OPLink.CssClass = "active";
        break;
        }
    }

I set the class to change to active when the value of the variable changes, but I'm not resetting the class to " ", as in, shouldn't be more correct to say

public void ActiveClass(string whatPage) {
        string PageName = whatPage;
        switch (PageName) {

        case "HomeLink":            
        HomeLink.CssClass = "active";
        AboutLink.CssClass = "";
        OPLink.CssClass = "";
        break;

        case "AboutLink":
        AboutLink.CssClass = "active";
        HomeLink.CssClass = "";
        OPLink.CssClass = "";
        break;

        case "OPLink":
        OPLink.CssClass = "active";
        AboutLink.CssClass = "";
        HomeLink.CssClass = "";
        break;
        }
    }

If it works without the above amendments, I take that it is taken care of somewhere

I set the class to change to active when the value of the variable changes, but I'm not resetting the class to " ",

This is set to "" when the page is created, so you are only setting the active page. It's not like the previous page is remembered and changed, it is recreated from scratch when you switch pages.

that (Master as Site)

The property Master refers to the base class, as you can see in the masterpage definition:

public partial class Site : System.Web.UI.MasterPage

Your method is in the Site class, that's why the cast is necessary.

Oh I see, so that "as" is effectively a cast operator. OK I think it's all clear!! thanks for your help

Be a part of the DaniWeb community

We're a friendly, industry-focused community of developers, IT pros, digital marketers, and technology enthusiasts meeting, networking, learning, and sharing knowledge.