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


Object Expected Error December 22, 2010 by Anonymous

Hi, Whenever i try to run your code, i get an object expected error in the javascript. I will mail you a snapshot of the error. Kindly explain why i am getting that error, so that i can resolve it. Please respond ASAP as i need to implement this immediately.

Paths December 14, 2010 by Christoff Truter

Perhaps the scripts running remotely are pointing to the wrong directory/folder for the js? Are you using the PHP or ASP.net version of this code?

Working Locally Remotely Not Working December 14, 2010 by jaganath rao

Please Want Help Immedately

asp net file picker December 5, 2010 by Anonymous

FileUltimate is an ASP.NET file manager control which you can add directly to your existing ASP.NET (.aspx) pages. The control renders a user interface similar to "Windows Explorer" within the page and this user interface allows you to view the contents of the predefined root folders and allow complete file management actions within those folders. File management actions can be limited by predefined permissions and quota limits on each folder separately.

November 28, 2010 by Christoff Truter

Simply retrieve it from the $picker object? You will notice two properties, lstFrom & lstTo, these properties contain the items you're looking for. E.g. on the page you're posting to you need to create an instance: $picker = new listpicker("picker"); Actually quite simple

Submit button November 28, 2010 by mosm

hello, im not sure regarding the "submit" button and to retrieve the items in the listbox from the right side.. do you know how? i'm new in php though.

move August 24, 2010 by Elumalai

so very nice coding....easily understand this code

August 19, 2010 by Christoff Truter

Hi John Thank you for your contribution. At a stage I am going to write a follow-up post (perhaps article) - or lets call it an evolution post of this one, which will include a number of ways to achieve this in ASP.net. Things like Json, JQuery etc etc, since JQuery seems to be a standard in ASP.net 4.0 - I was quite surprised at the beginning of the year when I installed VS 2010 and saw JQuery included in the project templates.(So I guess JQuery is something devs need to take more seriously) Anyway... thanks again :)

Thanks and here is a mod I made August 19, 2010 by John Lee

I don't know if you are still monitoring these posts but thank you for the code to move items between list boxes. I added a function to move them up and down in asp.net which needed your serialization to hidden field for .net to get the view state correct. If anyone is interested here is the code I added (do similar for move down): function moveUp(listID, containerID) { var theList = document.getElementById(listID); for (var i = 1; i < theList.options.length; i++) { if (theList.options[i].selected) { var oldValue = theList.options[i].value; var oldText = theList.options[i].text; theList.options[i].value = theList.options[i-1].value; theList.options[i].text = theList.options[i-1].text; theList.options[i-1].value = oldValue; theList.options[i-1].text = oldText; theList.options[i-1].selected = true; theList.options[i].selected = false; break; } } var container = document.getElementById(containerID); container.value = escape("<listboxes>" + serialize(theList) + "</listboxes>"); }

Re: 10+ Items May 8, 2010 by Christoff Truter

The items doesn't actually disappear, when moving an item from one list to another, it appends the moved items to the bottom of the list - hence the scrollbar. If you feel it isn't userfriendly, you can always rather prepend the moved items. e.g. in the listbox.js file at about line 19, change to.options.add(new Option(from.options[i].text,from.options[i].value)) to to.options.add(new Option(from.options[i].text,from.options[i].value), 0) Alternatively you can also resize the height of list (which brings you back to your previous issue)



Related Downloads

Moving items between listboxes