New to Telerik UI for ASP.NET AJAX? Download free 30-day trial

Grid with TreeView and ComboBox editors

The solution below illustrates how to implement treeview/combobox hybrid editor and dependent load-on-demand combobox editors in RadGrid for ASP.NET AJAX. The demo files can be downloaded from here.

The first type editor is useful when you would like to visualize hierarchical treeview structure for easier navigation among the combobox options when editing cell values (if applicable).

The second scenario is preferred when one wants to reduce the options in a combobox editor based on the selection in another dropdown editor (using the integrated callback mechanism of the filtered combo), thus receiving some performance gains since only the combobox is refreshed as opposed to the entire grid.

RadTreeView and RadComboBox editors in RadGrid

Examine the code implementation and the comments in the source for more details.

<style type="text/css">
    div.RadComboBoxDropDown .rcbItem
    {
        margin: 0;
        padding: 0;
    }
</style>
 <telerik:RadScriptBlock ID="RadScriptBlock1" runat="server">
    <script type="text/javascript">

        //stop the event bubbling
        function StopPropagation(e) {
            if (!e) {
                e = window.event;
            }

            e.cancelBubble = true;
        }

        //find the selected node in the treeview inside the combobox and scroll it into view
        function OnClientDropDownOpenedHandler(sender, eventArgs) {
            var tree = sender.get_items().getItem(0).findControl("RadTreeView1");
            var selectedNode = tree.get_selectedNode();

            if (selectedNode) {
                selectedNode.scrollIntoView();
            }
        }
        //when tree node is clicked, set the text and value for the item in the combobox and commit the changes
        function nodeClicking(sender, args) {

            //get the id of the employeesCombo in the edited row (passed from the server in the ItemDataBound event handler)
            var comboBox = $find(window['comboId']);
            var node = args.get_node();

            comboBox.set_text(node.get_text());

            comboBox.trackChanges();
            comboBox.get_items().getItem(0).set_text(node.get_text());
            comboBox.get_items().getItem(0).set_value(node.get_value());
            comboBox.commitChanges();

            comboBox.hideDropDown();

            // Call comboBox.attachDropDown if:
            // 1) The RadComboBox is inside an AJAX panel.
            // 2) The RadTreeView has a server-side event handler for the NodeClick event, i.e. it initiates a postback when clicking on a Node.
            // Otherwise the AJAX postback becomes a normal postback regardless of the outer AJAX panel.

            comboBox.attachDropDown();
        }
        function freightComboClientSelectedIndexChangedHandler(sender, eventArgs) {
            //get reference to the grid row DOM element
            var gridRow = sender.get_element().parentNode.parentNode;
            //locate the customers combobox in the same row using the $telerik.findControl method from the Telerik Client Static Library
            //note that the id of the combobox concatenates RCB_ + UniqueName value for the column, i.e. RCB_CustomerName in this particular case
            var customersCombo = $telerik.findControl(gridRow, "RCB_CustomerName");
            // this will fire the ItemsRequested server event and hook the OnClientItemsRequested client event of the
            // customers combobox passing the freight as a parameter to the first event
            customersCombo.add_itemsRequested(customersComboItemsRequested);
            customersCombo.requestItems(eventArgs.get_item().get_value(), false);
        }
        function customersComboItemsRequested(sender, eventArgs) {
            if (sender.get_items().get_count() > 0) {
                // pre-select the first item
                sender.findItemByText(sender.get_items().getItem(0).get_text()).select();
            }
            //detach the client items requested event as it not needed any more
            sender.remove_itemsRequested(customersComboItemsRequested);
        }
    </script>
</telerik:RadScriptBlock>
<telerik:RadScriptManager ID="RadScriptManager1" runat="server" />
<telerik:RadAjaxManager runat="server" ID="RadAjaxManager1" EnableAJAX="true">
    <AjaxSettings>
        <telerik:AjaxSetting AjaxControlID="RadGrid1">
            <UpdatedControls>
                <telerik:AjaxUpdatedControl ControlID="RadGrid1" LoadingPanelID="RadAjaxLoadingPanel1" />
                <telerik:AjaxUpdatedControl ControlID="lblMessage" />
            </UpdatedControls>
        </telerik:AjaxSetting>
    </AjaxSettings>
</telerik:RadAjaxManager>
<telerik:RadAjaxLoadingPanel ID="RadAjaxLoadingPanel1" runat="server" />
<asp:Label ID="Label1" runat="server" EnableViewState="false" Font-Bold="true" Text="Orders List" />
<telerik:RadGrid RenderMode="Lightweight" ID="RadGrid1" AutoGenerateColumns="false" PageSize="15" AllowPaging="true"
    AllowSorting="true" runat="server" DataSourceID="OrdersDataSource" AllowAutomaticUpdates="true"
    AllowAutomaticInserts="True" ShowStatusBar="true" OnItemDataBound="RadGrid1_ItemDataBound"
    OnUpdateCommand="RadGrid1_UpdateCommand" OnInsertCommand="RadGrid1_InsertCommand"
    OnItemCreated="RadGrid1_ItemCreated" onitemcommand="RadGrid1_ItemCommand">
    <MasterTableView DataKeyNames="OrderID" EditMode="InPlace" CommandItemDisplay="TopAndBottom"
        TableLayout="Fixed">
        <Columns>
            <telerik:GridBoundColumn UniqueName="OrderID" DataField="OrderID" HeaderText="OrderID"
                ReadOnly="true" />
            <telerik:GridDropDownColumn ListTextField="Freight" ListValueField="Freight" DataField="Freight"
                DataSourceID="ComboOrdersDataSource" HeaderText="Freight" UniqueName="Freight"
                AllowAutomaticLoadOnDemand="true" />
            <telerik:GridTemplateColumn UniqueName="LastName" HeaderText="Employee Name" SortExpression="LastName"
                ItemStyle-Width="400px">
                <ItemTemplate>
                    <%#DataBinder.Eval(Container.DataItem, "LastName")%>
                </ItemTemplate>
                <EditItemTemplate>
                    <!-- Show employees in the treeview -->
                    <telerik:RadComboBox RenderMode="Lightweight" ID="RadComboBox1" runat="server" Width="180px"
                        ShowToggleImage="True" Style="vertical-align: middle;" OnClientDropDownOpened="OnClientDropDownOpenedHandler"
                        ExpandAnimation-Type="None" CollapseAnimation-Type="None">
                        <ItemTemplate>
                            <div id="div1" onclick="StopPropagation(event);">
                                <telerik:RadTreeView RenderMode="Lightweight" runat="server" ID="RadTreeView1" OnClientNodeClicking="nodeClicking"
                                    Height="140px" DataTextField="LastName" DataValueField="EmployeeID" DataFieldID="EmployeeID"
                                    DataFieldParentID="ReportsTo" DataSourceID="EmployeesDataSource" />
                            </div>
                        </ItemTemplate>
                        <Items>
                            <telerik:RadComboBoxItem Text="" />
                        </Items>
                    </telerik:RadComboBox>
                </EditItemTemplate>
            </telerik:GridTemplateColumn>
            <telerik:GridDropDownColumn ListTextField="ContactName" ListValueField="CustomerID"
                DataField="CustomerID" DataSourceID="CustomersDataSource" HeaderText="Customer Name"
                UniqueName="CustomerName" AllowAutomaticLoadOnDemand="true" />
            <telerik:GridEditCommandColumn ButtonType="ImageButton" HeaderStyle-Width="60px" />
        </Columns>
    </MasterTableView>
</telerik:RadGrid>
<asp:Label ID="lblMessage" runat="server" EnableViewState="false" Font-Bold="true" />
<asp:SqlDataSource ID="OrdersDataSource" runat="server" ConnectionString="<%$ ConnectionStrings:NorthwindConnectionString %>"
    SelectCommand="SELECT orders.[OrderID], orders.[EmployeeID], orders.[CustomerID], orders.[Freight],
                          employees.[EmployeeID], employees.[LastName]
                          FROM [Orders] AS orders
                          INNER JOIN [Employees] AS employees
                          ON orders.EmployeeID = employees.EmployeeID"
    InsertCommand="INSERT INTO [Orders] ([EmployeeID], [CustomerID],[Freight]) VALUES (@EmployeeID, @CustomerID, @Freight)"
    UpdateCommand="UPDATE [Orders] SET [EmployeeID] = @EmployeeID, [CustomerID] = @CustomerID, [Freight] = @Freight WHERE [OrderID] = @OrderID">
    <InsertParameters>
        <asp:Parameter Name="EmployeeID" Type="Int16" />
        <asp:Parameter Name="CustomerID" Type="String" />
        <asp:Parameter Name="Freight" Type="String" />
    </InsertParameters>
    <UpdateParameters>
        <asp:Parameter Name="EmployeeID" Type="Int16" />
        <asp:Parameter Name="CustomerID" Type="String" />
        <asp:Parameter Name="Freight" Type="String" />
    </UpdateParameters>
</asp:SqlDataSource>
<asp:SqlDataSource ID="CustomersDataSource" runat="server" ConnectionString="<%$ ConnectionStrings:NorthwindConnectionString %>"
    SelectCommand="SELECT [CustomerID], [ContactName] FROM [Customers] Order By ContactName" />
<asp:SqlDataSource ID="ComboOrdersDataSource" runat="server" ConnectionString="<%$ ConnectionStrings:NorthwindConnectionString %>"
    SelectCommand="SELECT DISTINCT [Freight] FROM [Orders] Order By Freight" />
<asp:SqlDataSource ID="EmployeesDataSource" runat="server" ConnectionString="<%$ ConnectionStrings:NorthwindConnectionString %>"
    SelectCommand="SELECT [EmployeeID], [LastName], [ReportsTo] FROM [Employees]" />
protected void RadGrid1_ItemCreated(object sender, GridItemEventArgs e)
{
    //if a grid item is in edit mode, wire the ItemsRequested event of the customersCombo to filter the items in it based on the selection in the freight combo
    if (e.Item is GridEditableItem && e.Item.IsInEditMode)
    {
        RadComboBox freightCombo = (e.Item as GridEditableItem)["Freight"].Controls[0] as RadComboBox;

        if (Page.IsCallback)
        {
            RadComboBox customersCombo = (e.Item as GridEditableItem)["CustomerName"].Controls[0] as RadComboBox;
            customersCombo.ItemsRequested += new RadComboBoxItemsRequestedEventHandler(customersCombo_ItemsRequested);
        }
    }
}

protected void RadGrid1_ItemDataBound(object sender, GridItemEventArgs e)
{
    if (e.Item is GridEditableItem && e.Item.IsInEditMode && (!e.Item.OwnerTableView.IsItemInserted))
    {
        //wire the OnClientSelectedIndexChanged event of the first LOD combo editor to initiate callback on item selection
        RadComboBox freightCombo = (e.Item as GridEditableItem)["Freight"].Controls[0] as RadComboBox;
        freightCombo.OnClientSelectedIndexChanged = "freightComboClientSelectedIndexChangedHandler";

        //set the selected value for the Employees combobox to match the value in the edited cell
        RadComboBox employeesCombo = (e.Item as GridEditableItem).FindControl("RadComboBox1") as RadComboBox;
        employeesCombo.Items[0].Text = DataBinder.Eval(e.Item.DataItem, "LastName").ToString();

        //set the selection in the treeview inside the combobox to match the value in the edited cell
        RadTreeView employeesTreeView = employeesCombo.Items[0].FindControl("RadTreeView1") as RadTreeView;
        employeesTreeView.FindNodeByText(DataBinder.Eval(e.Item.DataItem, "LastName").ToString()).Selected = true;
        //Expand all nodes to scroll the selected into view. Have in mind that load-on-demand is recommended with very large number of nodes in the treeview
        employeesTreeView.ExpandAllNodes();

        //register script block which holds the client id of the employeesComboBox. Will be used to reference the client object of the combobox in the RadTreeView's OnClientNodeClicking event handler
        RadScriptManager.RegisterClientScriptBlock(this.Page, typeof(Page), "employeesComboScript", "<script type='text/javascript'>window['comboId'] = '" + e.Item.FindControl("RadComboBox1").ClientID + "';</script>", false);

    }
    else if (e.Item is GridDataInsertItem)
    {
        //wire the OnClientSelectedIndexChanged event of the first LOD combo editor to initiate callback on item selection
        RadComboBox freightCombo = (e.Item as GridDataInsertItem)["Freight"].Controls[0] as RadComboBox;
        freightCombo.OnClientSelectedIndexChanged = "freightComboClientSelectedIndexChangedHandler";

        //register script block which holds the client id of the employeesComboBox. Will be used to reference the client object of the combobox in the RadTreeView's OnClientNodeClicking event handler
        RadScriptManager.RegisterClientScriptBlock(this.Page, typeof(Page), "employeesComboScript", "<script type='text/javascript'>window['comboId'] = '" + e.Item.FindControl("RadComboBox1").ClientID + "';</script>", false);
    }
}

protected void customersCombo_ItemsRequested(object sender, RadComboBoxItemsRequestedEventArgs e)
{
    LoadFilteredCustomersManually(e.Text, sender as RadComboBox);
}

protected void LoadFilteredCustomersManually(string freight, RadComboBox customersCombo)
{
    //if the orderID value cannot be parsed as integer(i.e. auto LOD is triggered), exit the handler
    try
    {
        System.Double.Parse(freight);
    }
    catch
    {
        return;
    }

    SqlConnection connection = new SqlConnection(ConfigurationManager.ConnectionStrings["NorthwindConnectionString"].ConnectionString);
    connection.Open();

    //select customer name based on the orderID
    SqlCommand comm = new SqlCommand("SELECT CustomerID FROM [Orders] WHERE Freight=@Freight", connection);
    comm.Parameters.AddWithValue("@Freight", freight);

    string cid = comm.ExecuteScalar().ToString();

    comm = new SqlCommand("SELECT CustomerID, ContactName from [Customers] WHERE CustomerID = '"+ cid + "'", connection);
    SqlDataAdapter adapter = new SqlDataAdapter(comm);

    DataTable dt = new DataTable();
    adapter.Fill(dt);

    //populate the customers combo with the new set of items
    customersCombo.Items.Clear();

    foreach (DataRow row in dt.Rows)
    {
        customersCombo.Items.Add(new RadComboBoxItem(row["ContactName"].ToString(), row["CustomerID"].ToString()));
    }
    connection.Close();
}

protected void RadGrid1_UpdateCommand(object sender, GridCommandEventArgs e)
{
    string empId = (((e.Item as GridEditableItem).FindControl("RadComboBox1") as RadComboBox).Items[0].FindControl("RadTreeView1") as RadTreeView).SelectedNode.Value;

    //if the EmployeeID value is empty, cancel the update operation
    if(empId == string.Empty)
    {
        e.Canceled = true;
        SetMessage("Order cannot be updated. You must select an employee from the provided list");
    }
    else{
        OrdersDataSource.UpdateParameters["EmployeeID"].DefaultValue = empId;
    }
}

protected void RadGrid1_InsertCommand(object sender, GridCommandEventArgs e)
{
    //if the EmployeeID value is empty, cancel the insert operation
    if((((e.Item as GridEditableItem).FindControl("RadComboBox1") as RadComboBox).Items[0].FindControl("RadTreeView1") as RadTreeView).SelectedNode == null)
    {
        e.Canceled = true;
        SetMessage("Order cannot be inserted. You must select an employee from the provided list");
    }
    else
    {
        string empId = (((e.Item as GridEditableItem).FindControl("RadComboBox1") as RadComboBox).Items[0].FindControl("RadTreeView1") as RadTreeView).SelectedNode.Value;
        OrdersDataSource.InsertParameters["EmployeeID"].DefaultValue = empId;
    }
}

protected void RadGrid1_ItemCommand(object sender, GridCommandEventArgs e)
{
    //switch the edit/insert modes to ensure that only one operation will be processed at given time
    if (e.CommandName == RadGrid.EditCommandName)
    {
        e.Item.OwnerTableView.IsItemInserted = false;
    }
    else if (e.CommandName == RadGrid.InitInsertCommandName)
    {
        RadGrid1.EditIndexes.Clear();
    }

}

public void SetMessage(string message)
{
    lblMessage.Text = message;
}
Protected Sub RadGrid1_ItemCreated(ByVal sender As Object, ByVal e As GridItemEventArgs) Handles RadGrid1.ItemCreated
    'if a grid item is in edit mode, wire the ItemsRequested event of the customersCombo to filter the items in it based on the selection in the freight combo
    If TypeOf e.Item Is GridEditableItem AndAlso e.Item.IsInEditMode Then
        Dim freightCombo As RadComboBox = TryCast(TryCast(e.Item, GridEditableItem)("Freight").Controls(0), RadComboBox)

        If Page.IsCallback Then
            Dim customersCombo As RadComboBox = TryCast(TryCast(e.Item, GridEditableItem)("CustomerName").Controls(0), RadComboBox)
            AddHandler customersCombo.ItemsRequested, AddressOf customersCombo_ItemsRequested
        End If
    End If
End Sub

Protected Sub RadGrid1_ItemDataBound(ByVal sender As Object, ByVal e As GridItemEventArgs) Handles RadGrid1.ItemDataBound
    If TypeOf e.Item Is GridEditableItem AndAlso e.Item.IsInEditMode AndAlso (Not e.Item.OwnerTableView.IsItemInserted) Then
        'wire the OnClientSelectedIndexChanged event of the first LOD combo editor to initiate callback on item selection
        Dim freightCombo As RadComboBox = TryCast(TryCast(e.Item, GridEditableItem)("Freight").Controls(0), RadComboBox)
        freightCombo.OnClientSelectedIndexChanged = "freightComboClientSelectedIndexChangedHandler"

        'set the selected value for the Employees combobox to match the value in the edited cell
        Dim employeesCombo As RadComboBox = TryCast(TryCast(e.Item, GridEditableItem).FindControl("RadComboBox1"), RadComboBox)
        employeesCombo.Items(0).Text = DataBinder.Eval(e.Item.DataItem, "LastName").ToString()

        'set the selection in the treeview inside the combobox to match the value in the edited cell
        Dim employeesTreeView As RadTreeView = TryCast(employeesCombo.Items(0).FindControl("RadTreeView1"), RadTreeView)
        employeesTreeView.FindNodeByText(DataBinder.Eval(e.Item.DataItem, "LastName").ToString()).Selected = True
        'Expand all nodes to scroll the selected into view. Have in mind that load-on-demand is recommended with very large number of nodes in the treeview
        employeesTreeView.ExpandAllNodes()

        'register script block which holds the client id of the employeesComboBox. Will be used to reference the client object of the combobox in the RadTreeView's OnClientNodeClicking event handler

        RadScriptManager.RegisterClientScriptBlock(Me.Page, GetType(Page), "employeesComboScript", "<script type='text/javascript'>window['comboId'] = '" + e.Item.FindControl("RadComboBox1").ClientID + "';</script>", False)
    ElseIf TypeOf e.Item Is GridDataInsertItem Then
        'wire the OnClientSelectedIndexChanged event of the first LOD combo editor to initiate callback on item selection
        Dim freightCombo As RadComboBox = TryCast(TryCast(e.Item, GridDataInsertItem)("Freight").Controls(0), RadComboBox)
        freightCombo.OnClientSelectedIndexChanged = "freightComboClientSelectedIndexChangedHandler"

        'register script block which holds the client id of the employeesComboBox. Will be used to reference the client object of the combobox in the RadTreeView's OnClientNodeClicking event handler
        RadScriptManager.RegisterClientScriptBlock(Me.Page, GetType(Page), "employeesComboScript", "<script type='text/javascript'>window['comboId'] = '" + e.Item.FindControl("RadComboBox1").ClientID + "';</script>", False)
    End If
End Sub

Private Sub customersCombo_ItemsRequested(ByVal sender As Object, ByVal e As RadComboBoxItemsRequestedEventArgs)
    LoadFilteredCustomersManually(e.Text, TryCast(sender, RadComboBox))
End Sub

Protected Sub LoadFilteredCustomersManually(ByVal freight As String, ByVal customersCombo As RadComboBox)
    'if the orderID value cannot be parsed as integer(i.e. auto LOD is triggered), exit the handler
    Try
        Dim fid As Double = System.[Double].Parse(freight)
    Catch
        Return
    End Try

    Dim connection As New SqlConnection(ConfigurationManager.ConnectionStrings("NorthwindConnectionString").ConnectionString)
    connection.Open()

    'select customer name based on the orderID
    Dim comm As New SqlCommand("SELECT CustomerID FROM [Orders] WHERE Freight=@Freight", connection)
    comm.Parameters.AddWithValue("@Freight", freight)

    Dim cid As String = comm.ExecuteScalar().ToString()

    comm = New SqlCommand("SELECT CustomerID, ContactName from [Customers] WHERE CustomerID = '" + cid + "'", connection)
    Dim adapter As New SqlDataAdapter(comm)

    Dim dt As New DataTable()
    adapter.Fill(dt)

    'populate the customers combo with the new set of items
    customersCombo.Items.Clear()

    For Each row As DataRow In dt.Rows
        customersCombo.Items.Add(New RadComboBoxItem(row("ContactName").ToString(), row("CustomerID").ToString()))
    Next
    connection.Close()
End Sub

Protected Sub RadGrid1_UpdateCommand(ByVal sender As Object, ByVal e As GridCommandEventArgs) Handles RadGrid1.UpdateCommand
    Dim empId As String = TryCast(TryCast(TryCast(e.Item, GridEditableItem).FindControl("RadComboBox1"), RadComboBox).Items(0).FindControl("RadTreeView1"), RadTreeView).SelectedNode.Value

    'if the EmployeeID value is empty, cancel the update operation
    If empId = String.Empty Then
        e.Canceled = True
        SetMessage("Order cannot be updated. You must select an employee from the provided list")
    Else
        OrdersDataSource.UpdateParameters("EmployeeID").DefaultValue = empId
    End If
End Sub

Protected Sub RadGrid1_InsertCommand(ByVal sender As Object, ByVal e As GridCommandEventArgs) Handles RadGrid1.InsertCommand
    'if the EmployeeID value is empty, cancel the insert operation
    If TryCast(TryCast(TryCast(e.Item, GridEditableItem).FindControl("RadComboBox1"), RadComboBox).Items(0).FindControl("RadTreeView1"), RadTreeView).SelectedNode Is Nothing Then
        e.Canceled = True
        SetMessage("Order cannot be inserted. You must select an employee from the provided list")
    Else
        Dim empId As String = TryCast(TryCast(TryCast(e.Item, GridEditableItem).FindControl("RadComboBox1"), RadComboBox).Items(0).FindControl("RadTreeView1"), RadTreeView).SelectedNode.Value
        OrdersDataSource.InsertParameters("EmployeeID").DefaultValue = empId
    End If
End Sub

Protected Sub RadGrid1_ItemCommand(ByVal sender As Object, ByVal e As GridCommandEventArgs) Handles RadGrid1.ItemCommand
    'switch the edit/insert modes to ensure that only one operation will be processed at given time
    If e.CommandName = RadGrid.EditCommandName Then
        e.Item.OwnerTableView.IsItemInserted = False
    ElseIf e.CommandName = RadGrid.InitInsertCommandName Then
        RadGrid1.EditIndexes.Clear()
    End If

End Sub

Public Sub SetMessage(ByVal message As String)
    lblMessage.Text = message
End Sub
In this article