Wednesday 20 April 2011

How to edit nested ListView in ASP.NET

ListView is a cool control in ASP.NET. It is usefull for - well - listing data.

Even cooler you can nest a ListView inside another ListView if you need to group your list into groups - e.g. log grouped by date or an OrderHeader -> OrderLines grouping - and it is easy.

Unfortunately editing nested items is a bit clumcy - and it took me some time to figure it out. I have made an example here with two nested ListViews.

<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="WebForm1.aspx.cs" Inherits="NesterListTest.WebForm1" EnableEventValidation="false" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "
http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html xmlns="
http://www.w3.org/1999/xhtml">
<head id="Head1" runat="server">
    <title></title>
</head>
<body>
    <form id="form1" runat="server">
    <div>

        <asp:ListView ID="ListView1" runat="server" DataKeyNames="Id" OnItemDataBound="BindNestedList">
        <LayoutTemplate>
            <table id="RegistrationLines">
                <tr id="itemPlaceholder" runat="server"></tr>
            </table>
        </LayoutTemplate>
        <ItemTemplate>
            <tr><th>OuterIndex: <%# Eval("Id") %></th></tr>
           
            <asp:ListView ID="ListView2" runat="server" DataKeyNames="Id" OnItemEditing="EditNestedItem">
            <ItemTemplate>
                <tr>
                    <td><%# Eval("Value") %></td>
                    <td><asp:Button runat="server" ID="EditButton" CommandName="edit" Text="Edit" /></td>
                </tr>
            </ItemTemplate>
            <EditItemTemplate>
                <tr>
                    <td>Now editing -></td>
                    <td><%# Eval("Value") %></td>
                </tr>
            </EditItemTemplate>
            </asp:ListView>
        </ItemTemplate>
        </asp:ListView>  
    </div>
    </form>
</body>
</html>


In the Page_Load I just create some funke sample data. Note that I have two classes. These are just for holding the data - nothing else.


using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;

namespace NesterListTest
{
    public partial class WebForm1 : System.Web.UI.Page
    {
        List<TestContainer> list;

        protected void Page_Load(object sender, EventArgs e)
        {
            list = new List<TestContainer>();

            for (int i = 0; i < 5; i++)
            {
                TestContainer tc = new TestContainer() { Id = i };

                List<TestItem> l = new List<TestItem>();
                for (int j = 0; j < i % 3; j++)
                {
                    TestItem t = new TestItem() { Id = j, Value = "Cheese " + i + " > " + j };
                    l.Add(t);
                }
                tc.Items = l.ToArray();
                list.Add(tc);
            }

            ListView1.DataSource = list;
            ListView1.DataBind();
        }

        protected void BindNestedList(Object sender, ListViewItemEventArgs e)
        {
            if (e.Item.ItemType == ListViewItemType.DataItem)
            {
                ListViewDataItem item = (ListViewDataItem)e.Item;
                TestContainer container = (TestContainer)item.DataItem;
                ListView innerList = (ListView)item.FindControl("ListView2");
                innerList.DataSource = container.Items;
                innerList.DataBind();
            }
        }

        protected void EditNestedItem(Object sender, ListViewEditEventArgs e)
        {
            ListView lvs = sender as ListView;

            ListViewDataItem ctl = (ListViewDataItem)ListView1.FindControl(lvs.Parent.ID);
            if (ctl != null)
            {
                ListView lv = (ListView)ctl.FindControl("ListView2");
                lv.EditIndex = e.NewEditIndex;

                int indexOfParentControl = ListView1.Items.IndexOf(ctl);
                TestContainer t = list[indexOfParentControl];
                lv.DataSource = t.Items;
                lv.DataBind();
            }
        }
    }

    public class TestContainer
    {
        public int Id { get; set; }
        public TestItem[] Items { get; set; }
    }

    public class TestItem
    {
        public int Id { get; set; }
        public string Value { get; set; }
    }
}



In the EditNestedItem method, I find the parent control of the sender. As the sender is the inner ListView, the parent of this is a ListViewDataItem of the outer ListView. In the ListViewDataItem I find the inner ListView and set the EditIndex of it.
I then find the index of the ListViewDataItem in the outer ListViews items collection and now I have the index of the TestContainer for ListViewDataItem. Then I can grap it from the list and bind the Items of the TestContainer to the inner ListView.