Whenever I develop custom server controls, I pretty much always run into weird issues. I don’t know if it’s me, or server controls are tricky.
The past few days I’ve been polishing a composite control of my making, until it came time to wire some client-side script. In a nutshell, I’m building something like this:
[ToolboxData("<{0}:AdvancedList runat=server></{0}:AdvancedList>")]
public class AdvancedList : WebControl, INamingContainer
{
protected override void CreateChildControls()
{
DropDownList users = new DropDownList();
this.Controls.Add (users);
users.Items.Add ("John Doe");
users.Items.Add ("Jane Smith");
}
}
Nothing too fancy: a composite control which drives a dropdown list a certain way which makes sense to our product. I simply needed to add a snip of JavaScript to process the selection on the client-side. Now, oddly enough, as defined above, the control renders this:
<span id=”UserList”>
<select name=”UserList:_ctl0”>
<option value=”John Doe”>John Doe</option>
<option value=”Jane Smith”>Jane Smith</option>
</select>
</span>
Where does the outer <span> come from? It so happens that WebControl renders its child controls within a span. Crank up Reflector and peek inside WebControl’s Render method:
protected override void Render(HtmlTextWriter writer)
{
this.RenderBeginTag(writer);
this.RenderContents(writer);
this.RenderEndTag(writer);
}
WebControl’s constructor sets span as the default tag referred to above:
protected WebControl() : this(HtmlTextWriterTag.Span) { }
Quite annoying, I’d say. Also, the dropdown control has no id, which makes it complicated to locate it with plain DOM techniques, such as getElementById(). A quick search through newsgroups revealed a suggestion to overwrite the Render method:
protected override void Render(HtmlTextWriter writer)
{
RenderContents (writer);
}
Basically, we suppress the outer tag. Not a bad idea, except that the dropdown still has no id. Actually throwing it by hand into the Attributes collection does the job:
protected override void CreateChildControls()
{
DropDownList users = new DropDownList();
this.Controls.Add (users);
users.Items.Add ("John Doe");
users.Items.Add ("Jane Smith");
users.Attributes.Add ("id", this.ClientID);
}
This hodge-podge finally gave me what I was after:
<select name=”UserList:_ctl0” id=”UserList”>
<option value=”John Doe”>John Doe</option>
<option value=”Jane Smith”>Jane Smith</option>
</select>
Now, I imagine thousands of other people must’ve encountered this predicament before, and doing all this makes me feel dirty. Am I not seeing some painfully simple issue here? Can you share a better way?