0

I've tasked myself with creating a relatively simple user control (a bootstrap nav bar with some extra JS thrown in) just to increase my handle on how such things function. Ideally, it would look as follows:

<abc:NavBar ID="nvbTest" runat="server">
    <NavButtons>
        <abc:NavButton Target="Target1" />
        <abc:NavButton Target="Target2" />
        <abc:NavButton Target="Target3" />
    </NavButtons>
</abc:NavBar>

I've gotten to the point where I have the <NavButtons> section recognized by the compiler, but can't figure out how to get the inner <abc:NavButton> entries to work. Here's the code so far:

<div id="select" class="row">
    <ul class="nav nav-pills nav-justified">
        <asp:Literal ID="ltlNavButtons" runat="server" />
    </ul>
</div>
    [ParseChildren(true),PersistChildren(true),
    ToolboxData("<{0}:NavBar runat=server></{0}:NavBar>")]
    public partial class NavBar : System.Web.UI.UserControl, INamingContainer {
        /// <summary>
        /// Private collection of nav buttons.
        /// </summary>
        private NavButtonCollection _navButtons; 

        /// <summary>
        /// Singleton acquisition of nav button List.
        /// </summary>
        [PersistenceMode(PersistenceMode.InnerDefaultProperty)]
        public NavButtonCollection NavButtons {
            get {
                if (_navButtons == null)
                    _navButtons = new NavButtonCollection();
                return
                    _navButtons;
            }
        } 

        protected void Page_Load(object sender, EventArgs e) {
            foreach (NavButton nb in NavButtons) {
                ltlNavButtons.Text += nb.ToString() + Environment.NewLine;
            }
        }
    } 

    public class NavButtonCollection : List<NavButton> { } 

    public class NavButton {
        /// <summary>
        /// The target string for the nav button
        /// </summary>

        public string Target { get; set; } 

        public NavButton() {
            Target = string.Empty;
       } 

        public override string ToString() {
            return
                $@"<li role='presentation'>
                    <a id='a{Target}' data-toggle='div{Target}' class='btn btn-default'>
                        <b>{Target}</b>
                    </a>
                </li>";
        }
    }

I've been through many other answers here to get to this point, but all of the answers were rather old. I'm not sure if I'm hitting dead-ends because those pieces of code just aren't viable any more. I've also been attempting to use the MSDN for this, but the articles I've seen on 'template controls' either don't seem to cover what I'm looking to do, or are so esoteric in their presentation that the information they contain slides off my brain.

A direct answer on how to get the inner section to function would be great, though any direction from here would be very appreciated as well.

2

2 Answers 2

1

Well, it not clear if you looking to have sub menus.

However, keep in mind that anytime you add runat="server", then that element (even HTML ones) can now be used by code behind.

Next up, you should write down some kind of "goal" or what we often call a "spec" or specification of what you want to do!

Say, we want a user control that is a drop in menu bar.

So, if we take say this simple bootstrap menu bar:

    <div class="navbar navbar-inverse  navbar-fixed-top navbar-collapse collapse">
        <ul id="menuopts" runat="server" class="nav navbar-nav">
            <li><a runat="server" href="~/">Home</a></li>
            <li><a runat="server" href="~/Grids/Fighters">About</a></li>
            <li><a runat="server" href="~/Contact">Contact</a></li>
        </ul>
    </div>
    <div style="clear:both;height:60px"></div>

    <asp:Button ID="Button1" runat="server" Text="Button"  CssClass="btn"/>

(I tossed in a stray button for fun and giggles).

Ok, the above gets us this:

enter image description here

so, a good "specification" would be

I want a user control, and in the markup we can add/have a menu bar.

So, say this setup for a UC:

    <uc1:Menubar runat="server" ID="Menubar" 
        MenuText="Home;Fighters;Test" 
        MenuURL="~/;~/Grids/Fighters.aspx;~/Test1.aspx"
        />
    <div style="padding:35px">

        <h2>Some cool home page</h2>

    </div>

So, note above.

And if we make public members of that UC, then EVEN the property sheet shows above.

eg this:

enter image description here

so, each menu item is seperated by a ";", and each URL also is seperated by a ";"

So, now, when I drag + drop in that control, I get/see this:

enter image description here

Now, of course we could setup the UC to pull data (the menu) from say a databsae, or even more cool would be perahps to use json, and even support sub menus.

But, the above user control markup looks like this:

<%@ Control Language="C#" AutoEventWireup="true" 
    CodeBehind="Menubar.ascx.cs" 
    Inherits="CSharpWebApp.Controls.Menubar" %>

<div class="navbar navbar-inverse  navbar-fixed-top navbar-collapse collapse">
    <ul id="menuopts" runat="server" class="nav navbar-nav">
    </ul>
</div>
<div style="clear: both; height: 60px"></div>

And the code behind for the UC is this:

public partial class Menubar : System.Web.UI.UserControl
{

    public string MenuText { get; set; }
    public string MenuURL { get; set; }
    protected void Page_Load(object sender, EventArgs e)
    {
        HtmlGenericControl mbar = menuopts;
        string[] aMenuText = MenuText.Split(';');
        string[] aMenuURL = MenuURL.Split(';');
        
        for (int i = 0; i < aMenuText.Length; i++)
        {
            AddToMenu(mbar, aMenuText[i], aMenuURL[i]); 
        }
    }

    void AddToMenu(HtmlGenericControl mBar, string sMenuText, string sLink)
    {
        HtmlGenericControl myLI = new HtmlGenericControl("li");
        HtmlGenericControl myAref = new HtmlGenericControl("a");
        myAref.InnerText = sMenuText;
        myAref.Attributes.Add("href", ResolveUrl(sLink));
        myLI.Controls.Add(myAref);
        mBar.Controls.Add(myLI);
    }

}

So, above shows how we can "easy" add items to the menu bar with code.

As noted, when you think about the above example. the REAL question/challenge/hard part/part to think about/part to write down on paper?

Its where/what/how the source of the menu bar items is going to be defined, or come from.

As noted, I simple "cooked up" on the fly to have two settings

 MenuText items: delmited ";" text items for menu
 MenuURL items: delimited ";" text items of target URL's

So, really, the hard part was not having code add/create/insert a menu item, but the REAL hard part was cooking up what kind of "source" are we going to have that defines each menu item.

I mean, if we having to hand code out each menu, then we really not gained anything more then dropping in that origional bootstrap menu, and typing in the values in markup.

So, for example, one of the targets is ~/Grids/Fighers.aspx.

So, now, if I want to say drop in a UC menu to return to some home menu, then that target page could have this:

    <uc1:Menubar runat="server" ID="Menubar" 
        MenuText="Home" 
        MenuURL="~/MenuTest.aspx" />
    <div style="padding:30px">


<div style="float:left;margin-left:25px;width:50%">


<div style="float:left">

    <asp:GridView ID="GridView1" runat="server" AutoGenerateColumns="False"
        DataKeyNames="ID" CssClass="table table-hover" DataSourceID="SqlDataSource1"
        >
        <Columns>

etc. etc.

So, I just dropped in that UC, entered the URL (called it home).

So, now we get/see this effect by running our first page with the menu, and it launced the 2nd page.

So, with "great" easy, I was able to add a menu bar, and it certainly less effort/work then dropping in that whole bootstrap menu bar.

So, now I see,get this:

enter image description here

Sign up to request clarification or add additional context in comments.

5 Comments

While that's a pretty solid example (I seriously appreciate the effort, especially recording a GIF for it), there's one primary flaw: using any kind of delimited string in the initial signature really hurts the control's ability to have many buttons and still be relatively clean and readable code. That's why I was attempting to set up the nested controls in the first place.
Well, the issue/question (I suppose) is where are the choices to come from. Seems to me if they are "hard coded", then it not all that flexible. But, you could as noted pull from a database, or even have some json file (which then could even have drop down buttons - (hint: an array for those is suitable for json data)). So, I only put the choices in the markup since it makes the control rather flexible without hard coded choices. However, nothing suggests that the code used to fill out/add options has to be driven from the "split" of the choices in markup, and code can be used instead.
It is certainly possible I am not grasping the term "nested" here? I mean, the posted code does show how to add menu items across - and we can expand that code to have say one, or "some" of the menu across items also go "down" with a dropdown of choices - code will be rather similar anyway. So, I guess the missing part is where is the list of choices to come from for this control? (don't make sense to hard code such choices).
The primary goal was to end up with something similar in look/feel to an actual ASP.Net control. After creating the control, I didn't want to touch any codebehind for it at all. I suppose the next step would be further nesting a 'content' sub-control so I don't have to hard-code the container divs, but like my efforts to get the whole thing under one file, that's a completely different topic.
Well, I only floated the example with the ability to have a property that creates the menu on the fly - zero reason to adopt that part of my example. If you remove the public properties and read that menu data from a database, or even hard code the menu choices in the actual control example of mine? ,Then no such property setting with the menu choices is required anyway then, right? At that point you have a drag + drop menu bar control. (so, I am very much missing something here???). Heck, in place of code that "generates" the menu bar, you could put the whole markup in UC and be done then?
0

So it turns out my answer was to just create another user control (NavButton.ascx) and add that to the page as well.

Here's the NavBar.ascx setup:

<div id="select" class="row">
    <ul class="nav nav-pills nav-justified">
        <asp:Literal ID="ltlNavButtons" runat="server" />
    </ul>
</div> 

<script type="text/javascript">
    // Load cookies and set page state as necessary
    document.addEventListener("DOMContentLoaded", function () {
        var tab = getCookie(<%= $"\"{CookieName}\"" %>);
        if (tab != null) {
            // load active tab and set button to active
            var btn = tab.replace("div", "a");
            $("#" + btn).addClass("active");
            $("#" + tab).fadeIn();
        }
        else {
            // set noms/coms tab as active ('cause default)
            $(<%= $"\"#a{DefaultTab}\"" %>).addClass("active");
            $(<%= $"\"#div{DefaultTab}\"" %>).fadeIn();
        }
    }); 

    // Change content div displays and set ProfileTab cookie appropriately
    $('#select a').click(function () {
        // Remove active button CSS from old and apply to new
        $("#select a").removeClass("active");
        $(this).addClass("active"); 

        // Get div to show via data-toggle, fade out current div and fade in new
        var selector = $(this).data("toggle");
        $('#' + selector).siblings().fadeOut(400);
        $('#' + selector).delay(450).fadeIn(400); 

        // Set cookie for active profile tab
        setCookie(<%= $"\"{CookieName}\"" %>, selector, 7);
    });
</script>
    [ParseChildren(true),PersistChildren(true),
    ToolboxData("<{0}:NavBar runat=server></{0}:NavBar>")]
    public partial class NavBar : UserControl, INamingContainer {
        /// <summary>
        /// Name of the cookie to save the loaded tab.
        /// </summary>
        public string CookieName { get; set; } 

        /// <summary>
        /// Set the default tab to load without a cookie available.
        /// </summary>
        public string DefaultTab { get; set; } 

        /// <summary>
        /// Private collection of nav buttons.
        /// </summary>
        private NavButtonCollection _navButtons;

        /// <summary>
        /// Singleton acquisition of nav button List.
        /// </summary>
        [PersistenceMode(PersistenceMode.InnerProperty)]
        public NavButtonCollection NavButtons {
            get {
                if (_navButtons == null)
                    _navButtons = new NavButtonCollection();
                return
                    _navButtons;
            }
        } 

        protected void Page_Load(object sender, EventArgs e) {
            foreach (NavButton nb in NavButtons) {
                ltlNavButtons.Text += nb.ToString() + Environment.NewLine;
            }
        }
    } 

    public class NavButtonCollection : List<NavButton> { }

Here's NavButton.ascx (no html for this one, it's all c#):

    public partial class NavButton : System.Web.UI.UserControl {
        /// <summary>
        /// The text for the nav button.
        /// </summary>
        public string Text { get; set; } 

        /// <summary>
        /// The target string for the nav button
        /// </summary>
        public string Target { get; set; } 

        public NavButton() {
            Text = string.Empty;
            Target = string.Empty;
        } 

        public override string ToString() {
            return
                $@"<li role='presentation'>
                        <a id='a{Target}' data-toggle='div{Target}' class='btn btn-default'>
                            <b>{Text}</b>
                        </a>
                    </li>";
        }
    }

Actual implementation of the control is as follows:

    <div class="row">
        <abc:NavBar id="nvbTest" runat="server" CookieName="Test2" DefaultTab="Test3">
            <NavButtons>
                <abc:NavButton runat="server" Text="Test 1" Target="Test1" />
                <abc:NavButton runat="server" Text="Test 2" Target="Test2" />
                <abc:NavButton runat="server" Text="Test 3" Target="Test3" />
            </NavButtons>
        </abc:NavBar>
    </div>

    <div class="row">
        <div id="divTest1" class="col-md-12" style="display:none;">
            <h1>Test 1</h1>
        </div>
        <div id="divTest2" class="col-md-12" style="display:none;">
            <h1>Test 2</h1>
        </div>
        <div id="divTest3" class="col-md-12" style="display:none;">
            <h1>Test 3</h1>
        </div>
    </div>

My next step is to try and get it all in one file, just for the sake of organization. But that's a topic for another post all-together.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.