Achieving Synchronicity: A ListBox Double Feature

uilding complex Web controls with rich-client interfaces often requires the integration of some client-side JavaScript code with the control’s server-side code. While in some cases this does not have to be complicated to achieve some pretty nifty results, it can often break the data synchronization between the control’s internal server code and the rendered client HTML code. This becomes a problem when the page posts back. In this article, I will build two very cool Web controls that are vulnerable to this problem and then show you how to fix it.

The controls built in this article are designed for ASP.NET 2.0 for reasons explained later. Later in the article, I will give you a brief workaround explanation for getting them to work in ASP.NET 1.1 (or 1.0).

HTML remains the main language of Web applications at the very end of their rendering cycles. Unfortunately, the protocol that it rides on maintains a characteristic that Web developers have had to deal with known as statelessness. ASP.NET helps to deal with this with awesome architecture characteristics such as the postback mechanism and the ViewState variable. However, for certain functionality, it is still wise to put the extra effort and mix the technologies that lead to your Web page as opposed to constantly hitting the server using postback events. Specifically, I’m talking about using JavaScript and DHTML to access rendered elements on the client side. If you’re saying, “Great, we get the best of both worlds,” hang on, not so fast. Mixing client and server functionality requires a certain amount of finesse to achieve the nirvana that will contribute to the best user experience. Microsoft itself practices this belief as is demonstrated in the ASP.NET validation controls, which contribute a lot of JavaScript in order to provide a client-side validation experience.

Author’s Note: This article assumes a basic knowledge of custom Web control development as explained in “Custom Web Controls Demystified” published in CoDe Magazine Sept/Oct 2005 and Nov/Dec 2005. In the interest of space, I will not repeat basics of Web control development such as how properties work or how styling is added. I strongly encourage that you check out the previous articles and read the Web control tutorial I wrote.

A Conventional Approach
Get ready for a great ride because I’m going to show you how to build a couple of very cool controls with functionality that I have seen in commercial controls you would normally pay good money for. I will continuously demonstrate the benefit of full encapsulation that custom Web controls bring to Web programming. In my last couple of articles I taught you how to hit the ground running developing custom Web controls. One of the concepts I described was encapsulation of all functionality and behavior of a control as you would in a standard business object. This encapsulation will shine when you learn to build controls with complex behavior.

The first control I’ll show you how to build is called the EnhancedListBox. This control will extend the ASP.NET ListBox control by adding a heading and some reorder buttons. If you remember my “Custom Web Controls Demystified” articles (CoDe Magazine, Sep/Oct and Nov/Dec 2005) you will recognize this as an inherited control because it will directly inherit from the regular ListBox. Because of this, the control will serve as a drop-in replacement for the ListBox and will give you the ability to optionally display these extra features.

I’ll also show you how to build a composite control that will contain two of the above-mentioned EnhancedListBox controls. This control, called ListMover, will also contain some buttons that allow you to move list items from one list to another. You might have seen a control like this in Windows applications and maybe even on Web sites. So you may be asking, “What is the problem?”

Building sites with this type of functionality in ASP.NET is really not that difficult in the conventional method (non-Web control-oriented). First you would drop a regular ListBox control on your Web Form and fill it with some data (in whatever manner you want). Next you would add a label to serve as the heading and a couple of buttons to serve as the reorder buttons. Trapping the server side events for these buttons is standard operating procedure in ASP.NET so all you would need to figure out is a way to take the currently selected item and reposition it higher or lower in the list depending on which button the user clicked. You might write code like this for moving an item up in the list.

   i_Index = ListBox1.SelectedIndex;   o_Item = ListBox1.SelectedItem;   ListBox1.Items.RemoveAt(this.SelectedIndex);   i_Index--;   if(i_Index < 0) i_Index = 0;   ListBox1.Items.Insert(i_Index, o_Item);

Let me analyze what's happening here. First I'm saving the current Index and Item of the selected item in the list. Then I'm removing the item in the current position and reinserting it in a position one index lower. The logic behind his is pretty straightforward, so why am I demonstrating this? Using this conventional method of ASP.NET programming, the reorder buttons on the Web Form raise a server-side event that performs the reordering on the ListBox. This is triggered by a postback to the server and can be a heavy trip depending on what else is on the form as well as the speed of the Internet line.

However, because this code does a standard ASP.NET postback trip, ASP.NET takes care of handling state using its ViewState mechanism. When the page renders back, the contents of the ListBox are rendered correctly and in the order requested.

You can also reproduce this functionality in the ListMover control using the same conventional manner. For the purposes of brevity I won't reproduce it here but you can probably figure out how it goes. The Web Form would contain a couple of ListBoxes along with buttons signifying a move from left-to-right and a move from right-to-left. The server-side events for these buttons would extract the selected item from one ListBox and add it to the other and vice-versa. As in the first example, ViewState would work perfectly here to maintain the items in both of the ListBoxes.

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 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 tag and its Text property mapping to the Value attribute of the 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 items in the rendered control in this case), takes the selected index and moves it up or down in the list?basically the same thing I showed you using server code at the beginning of this article. Now, it's very important to understand where you're adding this JavaScript code from within the Web control. In order for this JavaScript code to NOT get duplicated in the case of there being more than one EnhancedListBox control on a Web Form, you need to output it using the RegisterClientScriptBlock 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 + ");   output.AddAttribute(HtmlTextWriterAttribute.OnClick,       s_MoveUp);

Remember MoveItemUp is one of the JavaScript functions you already wrote. This code will stack this JavaScript command before rendering the button used for ordering up. You'll use the same technique for the down button. Note that I used 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.

First I'll add a little more JavaScript to the mix. Listing 5 shows this JavaScript but I'll skip the other steps and tell you that it gets added in the OnInit method override, and built using the StringBuilder/StringWriter technique. Notice the name of the JavaScript method is BuildItemList. This function builds a string representation of the entire contents of the list box and places the string into the value property of an HTML element passed into the function. Think of this as a poor man's serialization of the list contents. The output style of the serialization can vary depending on your own design, but I used something from my past as specified in the "Delimiter Origins" sidebar. The call to this JavaScript function needs to get tacked on to the other code wired to the buttons.

   string s_MoveUp = "MoveItemUp(document.all." +       this.ClientID + "); ";   string s_BuildItemList =       "BuildItemList(document.all." + this.ClientID +       ",document.all.__" + this.ClientID + "); ";   output.AddAttribute(HtmlTextWriterAttribute.Onclick,       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 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 element) and in this case, move them over. As you can see, I've provided functions for adding items to a list, removing items from a list, as well as adding all and removing all from a list. I've separated the functionality instead of having single "move" methods so that I can make the removal of items from a list optional based on property settings. I think this gives the final control a bit more robustness but I won't walk through the code in this article. Also note I have added a BuildItemList method as in the previous control.

Now you need to wire this client code into the buttons of the composite control. You'll do this at the end of the CreateChildControls method where you initialized the child controls and built the Controls collection. I'll show you the code for one of the buttons but the others work the same way (don't forget to download the code).

   string s_AddToLeft =       "AddSelectedItemToList(document.all." +       this.lstItemsOnRight.ClientID + ", document.all."       + this.lstItemsOnLeft.ClientID + ", " +       (this.AllowDuplicatesOnLeft ? "true" : "false") +       "); ";   string s_RemoveFromRight =       "RemoveSelectedItemFromList(document.all." +       this.lstItemsOnRight.ClientID + "); ";   string s_BuildItemList =       "BuildItemList(document.all." +       this.lstItemsOnRight.ClientID + ",    document.all.__" + lstItemsOnRight.ClientID + "); " +       "BuildItemList(document.all." +       this.lstItemsOnLeft.ClientID + ", document.all.__"      + lstItemsOnLeft.ClientID + "); ";   this.btnAdd.Attributes.Add("onclick", s_AddToLeft       + " " + s_RemoveFromRight + " " + s_BuildItemList       + " return false");

Note that I'm doing the same thing I did in the previous control. I'm building the JavaScript function calls into a string and attaching them to a button. The main difference is that since this is a composite control containing other controls, you can use the Attributes.Add method of adding code to the onclick event as opposed to putting it in the stack as in a rendered control. Notice too that I placed more than one function in the onclick attribute. Also note that the end of the function calls return false in order to cancel whatever postback the button would execute.

Finally, the code will make the initial call to the client function BuildItemList in an override to the Render method. This looks just like the one I walked you through in the EnhancedListBox control so I won't repeat it here. In this control I registered two hidden text fields, one for each ListBox.

   protected override void OnPreRender(EventArgs e)   {      base.OnPreRender(e);      if(Page != null)      {             Page.ClientScript.RegisterHiddenField(             "__" + this.lstItemsOnRight.ClientID,             "");             Page.ClientScript.RegisterHiddenField(             "__" + this.lstItemsOnLeft.ClientID,             "");             Page.RegisterRequiresPostBack(this);      }   }

So you've built the composite control, provided some client-side JavaScript, and wired it into the buttons. As before, you can drop it on a form and use it; but it will suffer from the problem you had with the first control before you added the synchronization code. You can move items back and forth all you want but as soon as you initiate a postback (with any other control on the form) the control will revert back to its pre-postback state.

To fix this problem you'll pretty much do the same kind of thing you did in the first control. However, since you are programming a composite control, not extending an already existing control, you need to implement the IPostBackDataHandler interface and provide implementation for the LoadPostData and RaisePostDataChangedEvent methods. The implementation here (see Listing 7) is almost identical to that of the previous control except you'll synchronize the Items collections of two EnhancedListBox controls instead of just one. And as before, you need to make sure you save your SelectedIndex locations so you can set them back after you synch up the Items collection. Also note that in the first control you were overriding the LoadPostData method of the base control so at one point you called its base, Since you'll write a composite control from scratch there is no base to call and you simply need to provide the method's implementation.

The final version of this control contains several properties that add functionality including properties that determine whether the items added to one list will be removed from the other, and if a list will allow duplicate items. It also contains extensive styling for maximum reuse. In my "Custom Web Controls Demystified" article I stated that in custom control development, the more properties and styling you add to your controls, the larger your potential market will be and the more sites it can be integrated into.

That's about it. You've used hidden text fields, available from client-side script, to store the state of the list boxes, also available to use from client-side script. During the postbacks you used the contents of the hidden text fields to resynchronize with the server-side Items collection. The end result is a nice composite control that allows you to move list items back and forth with no server postback, yet keeps the changes made when a postback does occur.

Best of Both Worlds
One thing I didn't touch with too much specificity before is why I mixed the controls in this article. I started by enhancing a standard ListBox control then used two instances of this enhanced control in the ListMover control; I did not build ListMover with two standard ListBox controls. One part of the ListMover control I left out of this article is the properties that will map to the properties that were added to the EnhancedListBox control. This way I can control the enhanced functionality of the two EnhancedListBox controls from the ListMover control that contains them. So you can see that you have the best of both worlds here?you have a ListMover control that allows you to move list items between the two lists or you could reorder each list.

?
Figure 3. Controls Combined: By combining the ListMover and the EnchancedListBox controls, you give users complete control over two lists, letting them move items both between and within lists.

The real beauty here is something I mentioned in previous articles as one key benefit of Web control-oriented ASP.NET development-total encapsulation. The EnhancedListBox control contains all the code it needs to accomplish its goals?the reordering of its items. When I included two of them in the ListMover control, I got all the intelligence that came with them as extra functionality for the new control, including the client-side scripting each control includes and the client-to-server synchronization gave the EnhancedListBox control without having to worry about it in the ListMover control. The ListMover control simply had to worry about its own functionality. Figure 3 shows the ListMover control with the EnhancedListBox control's reorder buttons turned on.

I strongly encourage you to download the complete code for both these controls. Nothing compares to looking at a complete working product to fully understand its intricacies. Since I wrote these controls, I've used the ListMover quite a bit, not to mention a lot of downloads, and the EnhancedListBox control has pretty much replaced the standard ASP.NET ListBox in all my projects.

The techniques I used in this article demonstrate how to keep the server and client synchronized within a Web control and can be applied to many similar situations. While ASP.NET 2.0 introduces an intuitive interface for handling script callbacks, keeping functionality completely on the client remains the fastest solution and is compatible with ASP.NET 1.1 as well. I am, however, a big fan of the callback capabilities built into ASP.NET 2.0 and will cover them in future articles.

Share the Post:
Share on facebook
Share on twitter
Share on linkedin

Overview

Recent Articles: