Introduction
When working with internet-programs, you should reduce the number of post backs to the server to a minimum. This is an often heard sentence. When I was working on a site where I had a lot of these picklists on screen (see screenshot above) which can move items from the left to the right listbox and vice versa. I was in a hurry and didn't want to listen to the message of this sentence. So I made a user-control and some necessary events to be hooked upon in my code-behind. You can imagine that the client response was not so great. Now that I have more time to think it over, I came up with this custom control with client-side scripting that should have worked perfect for that site. But alas, the site is up-and-running so think of this as some fun to please you and me.
Using the code
Picklist
is a composite control, which consists of two ListBoxes
and four HtmlInputButtons
. I tried to name everything as common as possible. So, there is no 'Selected'-listbox or a 'DeSelectAll'-function but a left- and right listbox. I used HtmlInputButtons
for these can easilly have JavaScript connected to it.
The class PickList
is inherited from webcontrol. It implements the INamingContainer
and IPostBackDataHandler
. I choose for the WebControl
and not for the Control
class because it has some webcontrol properties like Height
and Width
for the layout of the listboxes. The INamingContainer
makes the naming of multiple picklists on a webform unique. The first PickList
gets a name like PickList1, the second one gets PickList2. The IPostBackDataHandler
is nessecary for getting values back out of some hidden fields which are being filled by JavaScript on the client. Getting values back on the server is done in the LoadPostData
method. Through the PostCollection
collection, the posted-back-values can be retrieved. It seems to me that this collection contains the same as the Page.Request.Forms
collection.
The working of PickList
is that you fill the listboxes with whatever you want during design-time or in code. When rendering the control to html, it saves the items from the listboxes separated with <tab>'s to viewstate which is done in the Page_Load
method. This way, we know what was originally in the listboxes when we get a postback. Next, in the OnPreRender
function, we render two hiddenfields to the client, which are later being filled by JavaScript on the client. In the beginning when making this control, I used two HtmlInputHidden
controls for storing values. I had to create these two controls in the CreateChildControls
, set there ID's and values and then add these to the controls collection. After searching the internet I coincidentally found the Page.RegisterHiddenField
function which works the Microsoft way and is used now, instead of the HtmlInputHidden
controls. Since this function, there is no need for setting the values and add it to the controls-collection. Ironically, the work saved, is not significant.
protected override void OnPreRender(EventArgs e)
{
base.OnPreRender(e);
if (Page != null)
{
Page.RegisterHiddenField(HelperLeftID, this.ValuesLeft);
Page.RegisterHiddenField(HelperRightID, this.ValuesRight);
};
}
On the client, the JavaScript moves the items from one listbox to the other when the user clicks a HtmlInputButton
. After this move, it sorts the collection whenever the IsSorted
property is set to true. Hereafter, another JavaScript function will copy the items in the listboxes, separated with <tab>'s, to the two hiddenfields which are being read on the server when a postback takes place.
When the postback take place, the two hidden fields contain the new values for the listboxes. In the IPostBackDataHandler.LoadPostData
method, the values are being read from the postCollection
and compared to the saved viewstate. When changed, the items are stored in the item-collection of the listboxes. The new viewstate value's can also be saved here, but it better be saved in the Page_Load
method. It seems that the collection has to be read again in the Page_Load
, and it does. So why do this twice? Because the first time the control is created, the LoadPostData
does not fire, but Page_Load
will. ViewState
has to be set for the control to work properly.
bool IPostBackDataHandler.LoadPostData(string postDataKey,
System.Collections.Specialized.NameValueCollection postCollection)
{
bool lblnReturn = false;
string NewLeftValue = postCollection[HelperLeftID];
string NewRightValue = postCollection[HelperRightID];
if (NewLeftValue != null)
{
EnsureChildControls();
if (String.Compare(this.ValuesLeft, NewLeftValue) != 0)
{
this.ItemsLeft.Clear();
foreach (string str in NewLeftValue.Split(strSep))
{
if (str != string.Empty)
this.ItemsLeft.Add(str);
};
lblnReturn = true;
}
}
return lblnReturn;
}
Consider when calling Page.RegisterClientScriptBlock
, always first call Page.IsClientSideScriptBlockRegistered
, which will avoid the performance taking call to <CODE>Page.RegisterClientScriptBlock
like this:
if (!Page.IsClientScriptBlockRegistered("PickListScript"))
Page.RegisterClientScriptBlock("PickListScript", script);
This way, what is done in code behind for making the script is omitted.
In the source of the control, JavaScript is set in code-behind. I could have chosen for a separate js-file, but I choose for better deployment. Also, by reading the property IsSorted
, the sort-function from the JavaScript can be switched off. When using a separate js-file should be some more coding.
Points of Interest
Databinding is not supported in this control because of simplicity. However, you can extent this control with databinding features from this good article http://www.codeproject.com/aspnet/webcontrolsdatabinding.asp. I have done it and it works great.
Also I thought it was difficult to implement child ListItems in the html-code like so:
<PickList id="PickList1">
<LeftListBox ID="left1">
<asp:ListItem Value="1">one</asp:ListItem>
<asp:ListItem Value="1">one</asp:ListItem>
</LeftListBox>
</PickList>
The solution was found at http://blogs.aspadvice.com/jlovell/archive/2004/02/29/663.aspx. I should have entered the ParseChildren(true)
and PersistChildrenattribute(false)
.
Also to view the ListBox
in design-time properly, set the height and width property to something like 150px. I did not support a designer-class for design-time support. The look and feel in design-time comes directly from the Render
method.
Hope you enjoyed it!
History