Achieving Synchronicity: A ListBox Double Feature : Page 2

See how to combine two Rendered controls to create a single control that provides the ability to move items both within and between lists.

by Miguel A. Castro

Jul 17, 2006

Page 2 of 4

The Intent
I'm not going to show you how to place all the individual controls used in the examples I just described on a Web Form; this would accomplish nothing toward reuse. As you probably guessed, I'll show you how to encapsulate the functionality described in each of the two examples into their own Web control. I'll show you how to encapsulate all the behavior to accomplish the necessary functionality right into each control using the same server-side event model described in the conventional approach examples. Since each control can control its own state, the Web Form containing them will not have any additional work to do except to sit back and enjoy the ride. So far so good, so where is the problem you may ask? Well, assuming page developers use these two controls on pages with a lot of content, reposting to the server anytime a reorder or a move takes place is not exactly an example of Web site efficiency. This is where a bit of JavaScript comes into the mix. You'll use JavaScript code to access the contents of the ListBox in the EnhancedListBox control in order to perform the reordering at the client. In the ListMover control, JavaScript code will move list items from one list to another. The end effect of this will be the same but with no server trip since no postback was triggered. Cool, so you can solve the problem with instant response time and no postbacks; what's the catch?

The Problem
ASP.NET has a very fine distinction between what happens on the server and what happens on the client. In fact, for the most part the two are very disconnected; and herein lies the problem. If you remember my last article, I told you that a Web control is simply a server-side component which renders HTML to the browser. Well, the standard ASP.NET ListBox control renders the only thing that can produce a ListBox in HTML, a <select> tag. The <option> tags within the <select> tag are created using the contents of the Items property in the ListBox control. The Items property is filled at the server and its contents help build the proper HTML during rendering. This is very similar to a Textbox Web control rendering an <input> tag and its Text property mapping to the Value attribute of the <input> tag. Every time a page postback is triggered to the server, the ListBox control's Item property is persisted to the ViewState and rebuilt from the ViewState just before the page re-renders.

Performing the reordering in the case of the EnhancedListBox, or moving items in the case of the ListMover at server-side is pretty straightforward and allows the built-in ViewState mechanism to function normally without any interference from us But when you add the ability to perform their function using client-side JavaScript, it throws a wrench into ViewState. The control(s) did NOT go back to the server so the Items collection property was never saved, to be reloaded at re-rendering. Instead the intent is to access the <option> items in the rendered <select> tag directly at the HTML level, way after the evidence of any existence of server-side counterparts has been eliminated. Using JavaScript code you can move or reorder items in the controls to your heart's content, but the second a postback occurs on the page, guess what happens? The control's list items revert back to their state before the moving (or reordering) began. In fact, the point they revert to is to the last postback or the initial page request if no postback had occurred.

Remember I said that if the functionality occurs at the server side during postbacks, ViewState is saved and reloaded fine, keeping the Items collection properly filled. But since your eventual goal is to perform this functionality on the client side, you are no longer properly adjusting the contents of the Items property, and it is this property that you're depending on for state storage. Now you can see the dilemma. But don't worryI wouldn't be writing this article if I didn't have a solution. So let's take this one step at a time and start by developing the controls with the necessary client-side script code to perform the functionality each needs. Then I will teach you how to resynchronize it back with the server code.

The EnhancedListBox Control
In building the two controls, I will expand on concepts and ideas I taught you in the last couple of articles.

For the first control you'll add two things to the existing ASP.NET ListBox control. First you'll add a heading. Hey, why not? Most of the time you probably drop a label above a ListBox anyway, so you might as well build it in. Afterward you'll add two buttons to the ListBoxone to reorder up and the other down.

For brevity, I will omit all attribute decorations from all code snippets going forward.

Create a new class and make it inherit from the ListBox control like this:

If you compile this code as-is and add the control to your toolbox, you'll have a fully functional replica of the ASP.NET ListBox control with no differences whatsoever from the original. I chose to develop this control as an inherited control because I want it to be a drop-in replacement for the ASP.NET ListBox control. Later I'll add properties that will allow the visibility of the heading and/or the reorder buttons to be turned off and on. When they are all turned off, the control will appear and behave exactly as a regular ListBox control. However, you cannot add controls to it compositely using an override to CreateChildControls because that's used to build a control hierarchy. The ASP.NET ListBox control was written as a Rendered Control and draws its entire HTML directly to the rendering engine so that is where you need to inject your stuff. You'll build the label and the two buttons using the Rendered Control approach, and render them by overriding the Render method. However, the instant you override this method, you completely cancel out all rendering in the original ListBox, and that's unacceptable. So I'm going to do something a bit tricky.

Code Injection
My solution will draw this control as if I created it as a standard Rendered Control including table tags and the rendering of the label and buttons I'm adding. When I get to the part of the display rendering where I want to inject the original ListBox that I inherited from, I will call the base.Render method. This will inject all the code that Microsoft wrote for the ListBox control into that section of HTML I am trying to draw (see Listing 1). The properties listed in Table 1 determine appearance and behavior for this code. I won't list the property code in this article but you can see it all in the downloadable code. Note in the code that the buttons you are rendering are set to cause a postback based on the value of a property called ReorderButtonPostback. The implementation of IPostBackEventHandler will capture this postback.

Now that the control looks the way you want (Figure 1), you can make the buttons do more than raise a postback. The finished product contains event handling code in the implementation of IPostBackEventHandler, as explained in my previous articles, so that events can be optionally raised to the server, in case a developer wants to execute further code at this point. But remember that you want to use these buttons to reorder the items in the ListBox and you want them to do it WITHOUT executing a postback. Now comes the fun part.