Moving items between listboxes in ASP.net/PHP example

June 12, 2008 by JavaScript   PHP   ASP.NET   Visual Basic  

Ever wanted to move items between two listboxes in ASP.net (or PHP for that matter)?



Someone wanted the exact same thing on codeproject.com a while ago but I didn't give much thought to it, until someone at work asked me how to do it - since he wasn't able to find a viable solution on the internet.

The solution I scripted wasn't as simple as I thought, nor would have liked it to be, my feeling was that one would be able to alter the two listboxes easily using javascript and simply "harvest" the changes (from the Request variables) when its submitted to the server.

The reality however is that only items selected(or highlighted) in the listboxes, will be returned to the server, not the state of the listboxes - so we need to somehow send the state of the listboxes along with the page request.

What I did was, I wrote a javascript script, that serialized the contents of the listboxes as xml to a hiddenfield on the page - whenever you submit the form to the server, a xml string containing the state of the listboxes get sent along - which one can easily process server side (which I will show you just in a while).

function move(fromID, toID, containerID)
{
    var from = document.getElementById(fromID);
    var to = document.getElementById(toID);

    for (var i = 0; i < from.options.length; i++)
    {
        if (from.options[i].selected)
        {					
            to.options.add(new Option(from.options[i].text,from.options[i].value))
            from.remove(i--);
        }
    }

    var container = document.getElementById(containerID);	
    container.value = escape("<listboxes>" + serialize(from) + serialize(to) + "</listboxes>");
}

function serialize(dropdown)
{	
    var value = '<' + dropdown.id + '>';	
    for (var i = 0; i < dropdown.options.length; i++)
    {
        value+= '<option><key><![CDATA[' + encodeURIComponent(dropdown.options[i].text) + ']]></key><value><![CDATA[' + encodeURIComponent(dropdown.options[i].value) + ']]></value></option>';
    }
    value+='</' + dropdown.id + '>';
    return value
}

function unselect(listbox)
{
    document.getElementById(listbox).selectedIndex=-1;
}


Tying it all together (javascript with some .net code), you can easily write yourself an usercontrol, like I did in my example, or create a composite control.

The .net code below is pretty straighforward, you'll noticed two publicly exposed ListBox properties (lstFrom, lstTo), giving us easy access to the listboxes.

If you really need to, you can easily add third, forth listboxes (with slight changes to the javascript code)

The PHP code is quite a bit more involved, I had to create a few classes that give similiar control of what you would expect from an ASP.NET server side control. There is a lot easier ways to do this, but I believe this is a fairly manageable approach.

using System;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Xml;

public partial class ListPicker : System.Web.UI.UserControl
{
    private XmlDocument _xmlDocument = new XmlDocument();

    public ListBox fromListBox
    {
        get
        {
            return lstFrom;
        }
    }

    public ListBox toListBox
    {
        get
        {
            return lstTo;
        }
    }

    private void PopulateListBox(ListBox listBox)
    {
        listBox.Items.Clear();
        XmlNodeList nodes = _xmlDocument.SelectNodes("listboxes/" + listBox.ClientID + "/option");
        foreach (XmlNode node in nodes)
        {
            listBox.Items.Add(new ListItem(HttpUtility.UrlDecode(node["key"].InnerText), HttpUtility.UrlDecode(node["value"].InnerText)));
        }
    }

    private void PopulateListBoxes()
    {        
        _xmlDocument.LoadXml(HttpUtility.UrlDecode(hdnDropdowns.Value));
        PopulateListBox(lstFrom);
        PopulateListBox(lstTo);
    }

    protected void Page_Load(object sender, EventArgs e)
    {
        Page.ClientScript.RegisterClientScriptInclude("listboxjs", Page.ResolveClientUrl("~/js/listbox.js"));
        if (!IsPostBack)
        {
            String movejs = "move('{0}','{1}','{2}')";
            String unselectjs = "unselect('{0}')";
            lstFrom.Attributes["onclick"] = String.Format(unselectjs, lstTo.ClientID);
            lstTo.Attributes["onclick"] = String.Format(unselectjs, lstFrom.ClientID);
            btnTo.Attributes["onclick"] = String.Format(movejs, lstFrom.ClientID, lstTo.ClientID, hdnDropdowns.ClientID);
            btnFrom.Attributes["onclick"] = String.Format(movejs, lstTo.ClientID, lstFrom.ClientID, hdnDropdowns.ClientID);
        }
        else
        {
            if (!(String.IsNullOrEmpty(hdnDropdowns.Value)))
            {
                PopulateListBoxes();
            }
        }
    }
}
Imports System.Xml

Partial Class ListPicker
    Inherits System.Web.UI.UserControl

    Private _xmlDocument As New XmlDocument()

    Public ReadOnly Property fromListBox() As ListBox
        Get
            Return lstFrom
        End Get
    End Property

    Public ReadOnly Property toListBox() As ListBox
        Get
            Return lstTo
        End Get
    End Property

    Private Sub PopulateListBox(ByVal lstBox As ListBox)
        lstBox.Items.Clear()
        Dim nodes As XmlNodeList = _xmlDocument.SelectNodes("listboxes/" + lstBox.ClientID + "/option")
        For Each node As XmlNode In nodes
            lstBox.Items.Add(New ListItem(HttpUtility.UrlDecode(node.Item("key").InnerText), HttpUtility.UrlDecode(node.Item("value").InnerText)))
        Next
    End Sub

    Private Sub PopulateListBoxes()
        _xmlDocument.LoadXml(HttpUtility.UrlDecode(hdnDropdowns.Value))
        PopulateListBox(lstFrom)
        PopulateListBox(lstTo)
    End Sub

    Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load

Page.ClientScript.RegisterClientScriptInclude("listboxjs", Page.ResolveClientUrl("~/js/listbox.js"))

        If Not IsPostBack Then
            Dim movejs As String = "move('{0}','{1}','{2}')"
            Dim unselectjs As String = "unselect('{0}')"

            lstFrom.Attributes("onclick") = String.Format(unselectjs, lstTo.ClientID)
            lstTo.Attributes("onclick") = String.Format(unselectjs, lstFrom.ClientID)
            btnTo.Attributes("onclick") = String.Format(movejs, lstFrom.ClientID, lstTo.ClientID, hdnDropdowns.ClientID)
            btnFrom.Attributes("onclick") = String.Format(movejs, lstTo.ClientID, lstFrom.ClientID, hdnDropdowns.ClientID)
        Else
            If Not String.IsNullOrEmpty(hdnDropdowns.Value) Then
                PopulateListBoxes()
            End If
        End If
    End Sub
End Class
<?php

require_once("controls.php");
require_once("listbox.php");

class listpicker extends controls
{
    var $lstFrom;
    var $lstTo;
    var $deserializable;

    function listpicker($id)
    {
        $this->attributes['id'] = $id;
        $this->lstFrom = new listbox($id.'_lstFrom');
        $this->lstFrom->attributes['style'] = 'width:200px';
        $this->lstFrom->attributes['onclick'] = "unselect('".$id."_lstTo')";	
        $this->lstTo = new listbox($id.'_lstTo');
        $this->lstTo->attributes['style'] = 'width:200px';
        $this->lstTo->attributes['onclick'] = "unselect('".$id."_lstFrom')";

        if ($_REQUEST[$id.'$hdnDropdowns'])
        {
            $this->deserializable = true;
            $this->xml(urldecode($_REQUEST[$id.'$hdnDropdowns']));			
        }	
    }

    function render()
    {	
    $id = $this->attributes['id'];
    $html='<table>
                <tr>
                    <td>'
                        .$this->lstFrom->render().
                    '</td>
                    <td>
                        <input id="btnTo" type="button" value=">>" onclick="move(\''.$id.'_lstFrom\',\''.$id.'_lstTo\',\''.$id.'_hdnDropdowns\')" />
                            <br />
                        <input id="btnFrom" type="button" value="<<" onclick="move(\''.$id.'_lstTo\',\''.$id.'_lstFrom\',\''.$id.'_hdnDropdowns\')" />
                    </td>
                    <td>'		
                        .$this->lstTo->render().
                    '</td>
                </tr>
            </table>
	        <input type="hidden" ID="'.$id.'_hdnDropdowns" name="'.$id.'$hdnDropdowns" />';		
    return $html;
    }

    function xml($text) 
    {		
        $id = $this->attributes['id'];
        $parser = xml_parser_create();
        xml_parse_into_struct($parser, $text, $vals);
        xml_parser_free($parser);
        $tag = "";

        for($i = 0; $i < count($vals); $i++)
        {	
            switch($vals[$i]['tag'])
            {
                case strtoupper($id.'_lstFrom') : $tag = "lstFrom";	break;
                case strtoupper($id.'_lstTo') : $tag = "lstTo";	break;
            }

            if (($tag) && ($vals[$i]['tag'] == "KEY"))
            {
                $this->{$tag}->addItem($vals[$i]['value'], $vals[$i+1]['value']);					
            }
        }
    }
}

?>


Leave a Comment


10+ items? May 7, 2010 by Kris

and I am using php and have actually 150 records to show!

10+ items? May 7, 2010 by Kris

When you have abt 20 Records, in short when a horizontal scroll bar appears in the from list -> and you move items from left to right. Try moving items back from right to left. The item dissapears. Is there any solution to that? Please reply back.. It would be helpful!

Sorting ASP.Net ListItems April 22, 2010 by Rob Bratton

/// <summary> /// Sorts the list items as an integer with five digits - Store Number /// </summary> /// <param name="items">The list items to be sorted</param> private void SortStoreListItem (ListItemCollection items) { SortedList<int, string> sl = new SortedList<int, string>(); foreach (ListItem item in items) { sl.Add(int.Parse(item.Text), item.Value); } items.Clear(); foreach (KeyValuePair<int, string> item in sl) { items.Add(new ListItem(item.Key.ToString("00000"), item.Value)); } } Enjoy! Rob

RE: Iteration February 3, 2010 by bri

Yes Please! It would make a big difference to have other ways of using your code

Iteration February 3, 2010 by Christoff Truter

Basically you'll need to iterate through your query results, adding items to the listbox via your iteration, do you need an example of how to do this?

HELP February 2, 2010 by bri

thanks that worked. If you dont mind me asking how would i work a list box into this where the content comes from a database and not your list. So box 1 is "NewTownList" dynamically populated using sql SELECT Query "this works" just need to know how i would tie "NewTownList" into your code Thanks a mil

php tag February 2, 2010 by Christoff Truter

try replacing <?=$picker->render(); ?> with <?php echo $picker->render(); ?> on the index.php page

Hmmm February 2, 2010 by bri

render(); ?>, this is all i see when i run the index.php

Reason January 14, 2010 by Christoff Truter

Thank you for the feedback. This is actually by design. I went on the assumption that if no xml are available it means the user didnt alter anything to the lists - hence no editing happend - in turn no update needed. But I can understand that it might be required to pass it either way in certain cases. (But generally one can simply return the original values from the db either way - if needed)

Need an onload serialize function January 14, 2010 by Shawn Brant

One thing I noticed that I had to do to get this working for me was that if the user does not touch the list you do not setup the xml. You only do this when they move an item. This is OK if it is a new form but if you are using this on say an edit feature it shows nothing once they submit the form under values. I just did a function that called your serialize function and did an onload call. This is under PHP BTW. Otherwise it is a great script!



Related Downloads

Moving items between listboxes