Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

PickList

0.00/5 (No votes)
9 Jun 2004 9  
Two listboxes to choose from and client-side scripting to move items from left to right and vice versa

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 the user changes Input value, update the text property. 

        if (String.Compare(this.ValuesLeft, NewLeftValue) != 0) 
        { 
            this.ItemsLeft.Clear(); 
            foreach (string str in NewLeftValue.Split(strSep)) 
            { 
                if (str != string.Empty) 
                // Here ViewState could easilly be set 

                // Something like: this.ValuesLeft += str

                    this.ItemsLeft.Add(str); 
            }; 
            // Returning true invokes RaisePostDataChangedEvent. 

            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

  • Version 1.0, June 2004.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here