Adding the Client-side Functionality
method from the Page.ClientScript object.
For this method to be functional, the OnInit
event must call it, which you must override (see Listing 4
The last thing you need to do to get the buttons to work right is to wire the client methods you added to them. In the code in Listing 1
you saw a reference to a method called RenderButtons
. Though I'm not including that code here (it's in the downloadable code), I'll tell you that it renders buttons using the techniques I taught in my previous article. If you look back at that article, you see that I taught you that tag attributes are "stacked" using the AddAttribute
method before the actual HTML tag is rendered. You'll use this same technique to wire the client-side methods to your buttons.
string s_MoveUp = "MoveItemUp(document.all." +
this.ClientID + ");
to represent the ID of the control after rendering. In the case that this control would exist inside another composite control, this property would takes parent naming into consideration.
At this point, I want to remind you to download the finished controls as specified in the "Where to Download" sidebar, so you can see all this code properly put together into the finished product.
As the control stands now, you can use it on a Web Form successfully. You can add items to it the same way you would to a standard ListBox control. In fact, this is a complete drop-in replacement or the ASP.NET ListBox control. When you use the reorder buttons, you'll see the items in the list change order accordingly. OK, let's recall "the problem" now. If you drop a button on a Web form (no code needed for it) and you execute a postback, what do you think will happen? Well, exactly what I described earlier; any reorder changes you made with the reorder buttons will revert to whatever the control looked like before the last postback. So let's fix this.
. This function builds a string representation of the entire contents of the list box and places the string into the value
string s_MoveUp = "MoveItemUp(document.all." +
this.ClientID + "); ";
string s_BuildItemList =
"BuildItemList(document.all." + this.ClientID +
",document.all.__" + this.ClientID + "); ";
MoveUp + " " + BuildItemList);
Look at the two arguments you're sending into the BuildItemList
function. The first corresponds to the ID of the rendered control (the <select> tag). The second is another ID, named the same but prefaced with a "__". This is a hidden textbox you still need to add to your Web control and it's going to act as a place holder for the "serialized" item list. I'm going to register the hidden text field in the OnPreRender
protected override void OnPreRender(EventArgs e)
if(Page != null)
"__" + this.ID, "");
Notice that I've used the ID of our control to identify the hidden text field.
Where Are You So Far?
server property of the control has not received any of the changes you made to it when reordering; but luckily you have those changes in the form of a serialized snapshot in the hidden text field. Have you painted a mental picture yet? You now have something that you can use to synchronize with the Items
property. So how do you do this?
To perform the synchronization upon the first postback and on every subsequent one, ASP.NET has a LoadPostData
method in the implementation of the IPostBackDataHandler interface. The LoadPostData
method gets called on every postback so this is where you need to do some work.
Earlier I told you that I'd explain why I'm writing these controls using ASP.NET 2.0. Besides the fact that you should already be embracing it now, ASP.NET 2.0 fixes a small oversight left out in 1.1 that makes your job here incredibly easier. The ASP.NET ListBox control already implements the IPostBackDataHandler interface in both versions (1.1 and 2.0). But in 2.0 Microsoft made the method definitions within it virtual (Overridable in VB
). This means you don't have to reimplement this interface in the EnhancedListBox control; instead you can just override the LoadPostData
method. More importantly, this also means you can access the base implementation and not have to reproduce all that functionality in your extended control. What functionality you ask? Everything Microsoft put in there to handle the Items
collection, the SelectedIndex
, and SelectedItem
properties, and a whole other slew of code to perfect the functionality of the ListBox control. In ASP.NET 1.1 you would have to implement this interface in your derived control and provide your own code for the two methods defined, covering not only your own additions but also repeating everything that Microsoft already did in their control. This would be an incredible amount of work and would not only quadruple the length of this article, but would drive my hands into yet another stage of carpal tunnel syndrome.
I guess someone in Redmond saw the error in their ways and made the methods virtual so developers can have access to Microsoft's base code. So that's exactly what I'm going to show you how to do now, override the LoadPostData
method (see Listing 6
). In this override, you'll call the base implementation first so you can get Microsoft's code properly executed and not mess anything up that they intended for the ListBox control. You'll then add the code you need to synch up the Items
Alternatively, you could leverage the ListBox control by writing this as a composite control. In that case you would need to map out every property in the ListBox to your EnhancedListBox in order to make it a drop-in replacement for the ListBox. Either this way, or in the recreation of the LoadPostData
method, you would still need to write a lot of code. If I needed to write this control specifically for ASP.NET 1.1, I would most likely take the most straightforward solution, the composite control solution.
As I explained in my last article, the LoadPostData
method gives you access to every field posted to the server, including your hidden text field. This is stored in the postCollection
argument passed into this method. If you wondered why you needed the hidden text field instead of accessing the posted <select>
element using this argument, I'll tell you why. Think back to the classic ASP days when you used the Request.Form
property to access page fields. The only part of a <select>
element you had access to at posting was the selected item. In the solution I'm teaching you, you need the entire contents of the list, hence the hidden text field. Listing 6
shows how to parse out the contents of the hidden text field and re-add the items into the Items
collection. There's also code there to hang on to what the selected index was before you cleared out the Items
collection so you can reset it later. Note how you're calling the base implementation first.
Finally you have to build the hidden text field the first time you render your control, in case the page postbacks before any attempts at reordering take place. The last line of the Render
method will be:
BuildItemList(document.all." + this.ClientID +
",document.all.__" + this.ClientID +
You can see this at the end of Listing 1