简体   繁体   中英

ASP.Net user control with nested controls

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.

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:

在此处输入图像描述

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:

在此处输入图像描述

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:

在此处输入图像描述

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:

在此处输入图像描述

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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM