views:

500

answers:

1

This should be relatively easy. I do not know why I am struggling with it, but Google cannot help me find an example of what I want to do.

I am building a navigation/menu out a simple nested structure of ULs with LIs. The parent object that is placed on the page is called NavController. It has a collection of NavItems.

So I pretty much let the NavController handle building the base UI, creating the CSS, and JavaScript. It also creates the base UL for the top most level of LIs. From my research and understanding the UI creation should be done in the CreateChildControls method.

Here is my code:

protected override void CreateChildControls()
{
  Controls.Clear();

  this.Page.RegisterClientJavaScript("~/Resources/Script/SideNav.js");
  _ClientObject = this.GetClientJavaScriptObject("SideNav", this.ClientID);

  Controls.Add(_BaseContainer);

  HtmlGenericControl innerContents = this.BuildBaseContainer();
  this.BuildList(innerContents);

  _ClientObject.AddParameter("BaseContainerID", _BaseContainer.ClientID, true);
  _ClientObject.AddParameter("ImgId", _SideBarTabImg.ClientID, true);
  _ClientObject.AddParameter("SideBarContentsId", _SideBarContents.ClientID, true);

  base.CreateChildControls();
}

The BuildList method actually builds the top level LIs. I am unsure how this method is called, but I can see that it is. Inside the BuildList function I add to the innerContents's Controls collection the NavItem objects.

First off, is this correct? Placing another custom server control inside of of a UL (with the caveat that the custom control is basically rendering an LI; more on this later)?

The CreateChildControls is never being called for the NavItem. After looking around on the Internet it seemed like it was because I was never calling EnsureChildControls. This confuses me because It will only fire when the ChildControlsCreated property is false. Once it is run this property becomes true. If I have a Text and Href on the NavItem and EnsureChildControls is called when either of them are set then how will the other's value be placed into any sub control?

Just to clog up some screen space here is the code that I have so far for the NavItem:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Text;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.HtmlControls;

namespace AthletesCafe.Web.WebControls.SideNav
{
  [ToolboxData("<{0}:NavItem runat=server></{0}:NavItem>")]
  public class NavItem : ExtendedWebControl
  {
    private string _Text;
    private string _Value;
    private IList<NavItem> _ChildItems = new List<NavItem>();

private HtmlGenericControl _ListItem = new HtmlGenericControl("LI");
private HtmlAnchor _Link = new HtmlAnchor();
private HtmlGenericControl _SubList = new HtmlGenericControl("UL");

public string Text
{
  get
  {
    EnsureChildControls();
    return _Text;
  }

  set
  {
    EnsureChildControls();
    _Text = value;
    _Link.InnerText = _Text;
  }
}

public string Value
{
  get
  {
    EnsureChildControls();
    return _Value;
  }

  set
  {
    EnsureChildControls();
    _Value = value;
    _Link.HRef = _Value;
  }
}

protected override void CreateChildControls()
{
  Controls.Clear();
  Controls.Add(_ListItem);

  _ListItem.Controls.Add(_Link);

  base.CreateChildControls();
}

public NavItem AddChildItem(string text, string url)
{
  EnsureChildControls();

  NavItem newItem = new NavItem();
  newItem.Text = text;
  newItem.Value = url;

  _ChildItems.Add(newItem);
  this.BuildSubList();
  return newItem;
}

private void BuildSubList()
{
  _SubList.Controls.Clear();

  if (_ChildItems.Count > 0)
  {
    if (!_ListItem.Controls.Contains(_SubList))
      _ListItem.Controls.Add(_SubList);

    foreach (NavItem item in _ChildItems)
      _SubList.Controls.Add(item);
  }
}

protected override void Render(HtmlTextWriter writer)
{
  RenderContents(writer);
}

protected override void RenderContents(HtmlTextWriter writer)
{
  _ListItem.RenderControl(writer);
  //base.RenderContents(writer);
}

    public override string ToString()
    {
      EnsureChildControls();
      return _Text + " - " + _Value;
    }
  }
}

I don't understand why I can't just set the value and text in their public properties and then use some method or manner to place it into the controls before render.

Please, please some point me to the correct way to do this. I have a bunch of these little controls that I need to make that basically do this same approach. I'm going to start a rudimentary toolbar soon and this information would help!

EDIT - Placing in code that shows the list building for clarification:

HtmlGenericControl list = new HtmlGenericControl("UL");
innerContents.Controls.Add(list);
foreach (NavItem item in _Items)
{
  list.Controls.Add(item);
}
+2  A: 

Once you override CreateChildControls(), you basically become responsible for making sure that your nested controls get created properly.

Your approach seems pretty standard, however, for your simple properties, I do not think you need to be calling EnsureChildControls(). If you picked this up from the MSDN docs on the function, take another look - the "Text" property example that they give is retrieving the text value from one of its child controls, which means that the property accessor needs to make sure the child controls exist.

You cannot guarantee when someone might access your control, which is why you would put in EnsureChildControl() calls before any access to a control in your Controls collection. In a normal page lifecycle, CreateChildControls() will get called when ASP.net gets around to adding your composite control to the page. This may not happen until PreRender on the first page load if nobody touches your control and it doesn't databind or anything.

On subsequent postbacks, CreateChildControls will be called during ViewState processing, or any code might touch off a composite control to start creating it's child controls. FindControl() will do it automatically, for example - and this could happen in OnInit().

Hope that helps. I think you're on the right track.

womp
Looking at the list code I added, can you explain why when I add the NavItem to the UL it does not ever render. By render, I mean I do not know where in it I should place the code that creates its child controls. I would think it would be the same method.
Mike G