ASP.NET DropDownList generated by repeater not firing OnSelectedIndexChanged after being created

42 Views Asked by At

Bit of a tough bug I'm dealing with. I've created an ASP DropDownList using a Repeater like so in my .aspx file:

mypage.aspx

 <asp:Repeater ID="rptTemplateFields" runat="server"
                      DataSource='<%# rptrDataSource %>' 
                      OnItemDataBound="rptTemplateFields_OnItemDataBound">
    <ItemTemplate>
        <asp:DropDownList ID="ddlRepeater" runat="server" AppendDataBoundItems="true"
                      AutoPostBack="True" 
                      OnSelectedIndexChanged="ddlRepeater_SelectedIndexChanged"
                      width="360" Visible="False">
            <asp:ListItem Text="" Value="" />
        </asp:DropDownList>                     
    </ItemTemplate>
</asp:Repeater>

And I'm populating these dropdowns with the following in my codebehind:

mypage.aspx.cs

protected void rptTemplateFields_OnItemDataBound(object sender, RepeaterItemEventArgs e)
{
    if (e.Item.ItemType == ListItemType.Item ||
        e.Item.ItemType == ListItemType.AlternatingItem)
    {
        dynamic field = e.Item.DataItem;
        BugFieldName fieldName = field?.FieldName;
        string fieldContent = field?.FieldContent;

        var allowedValues = GenerateAllowedValues(fieldName);

        if (!fieldName.IsNullOrUnsetValue() && !allowedValues.IsNullOrEmpty())
        {
            var ddl = e.Item.FindControl("ddlRepeater") as DropDownList;
            ddl.ID = "_ddlField" + fieldName;
            ddl.Attributes.Add("Name", "_ddlField" + fieldName);

            ddl.ClientIDMode = ClientIDMode.Static;
                

                    ddl.DataSource = allowedValues;
            ddl.SelectedValue = allowedValues.Contains(fieldContent)
                ? fieldContent
                : null;
            ddl.Visible = true;
            ddl.DataBind();
        }
    }
}

protected void ddlRepeater_SelectedIndexChanged(object sender, EventArgs e)
{
    var ddlRepeater = sender as DropDownList;
    var state = (string)ViewState[ddlRepeater.Attributes["Name"]];

    if (state != ddlRepeater.SelectedItem.Text)
    {
        ViewState[ddlRepeater.Attributes["Name"]] = ddlRepeater.SelectedItem.Text;
    }
}

The problem is the first time I click the drop down list and change the item (immediately post creation and databind), a page load is triggered and the ddl.SelectedValue, etc. state is wiped and the DropDownList's "OnSelectedIndexChanged" event handler does not fire.

Immediately following this page load, subsequent changes in the DropDownList DO trigger OnSelectedIndexChanged and the list's state is preserved. I imagine something funky is going on with the postback and data binding, but I'm not sure where I'm going wrong.

So far I've tried using other event handlers without any luck. I've confirmed that a page load does occur and it is a PostBack. I added <%@ Page ViewStateMode="Enabled" %> to my aspx file. I also tried using an ItemCommand listener on the repeater. This also did not fire.

1

There are 1 best solutions below

9
Albert D. Kallal On

Ok, this issue looks to be that you set the data source of the repeater in the markup. So, with any post-back, your re-load and re-binding code runs again, and that results in the loss of any selecting you make (re-binding reruns your row data bound code again and again). So, any changes you make are lost due to the post back and the reloading of data again.

The simple solution is to bind the data to the repeater in code behind, and ONLY one time.

Hence, say this simple markup:

<div style="float: left; width: 30%">
    <asp:Repeater ID="Repeater1" runat="server"
        OnItemDataBound="Repeater1_ItemDataBound">
        <ItemTemplate>
            <h4>Booking Infomration</h4>
            <asp:Label ID="lblPerson" runat="server" Width="200px"
                Text='<%# Eval("FirstName") + " " + Eval("LastName") %>'>
            </asp:Label>

            <asp:Label ID="Label1" runat="server" Text="Hotel:"
                Style="margin-left: 30px">
            </asp:Label>

            <asp:DropDownList ID="cboHotel" runat="server"
                DataValueField="ID"
                DataTextField="HotelName"
                AutoPostBack="true"
                OnSelectedIndexChanged="cboHotel_SelectedIndexChanged">
            </asp:DropDownList>
            <br />
            <hr style="height: 2px; border: none; background-color: black" />

        </ItemTemplate>
    </asp:Repeater>
</div>

<div style="float: left; margin-left: 30px">
    <h3>Combo box selected row information</h3>
    <asp:TextBox ID="txtInfo" runat="server"
        TextMode="MultiLine" Height="160px" Width="350px"></asp:TextBox>
</div>

And our code behind, and note VERY close how we ensure only a binding of the data on the first page load (IsPostBack = false).

Hence this code:

    protected void Page_Load(object sender, EventArgs e)
    {
        if (!IsPostBack)
            LoadData();
    }

    void LoadData()
    {
        string strSQL =
            @"SELECT ID, HotelName FROM tblHotels
            ORDER BY HotelName";

        rstHotels = General.MyRst(strSQL);

        strSQL = "SELECT * FROM People ORDER BY FirstName";
        Repeater1.DataSource = General.MyRst(strSQL);
        Repeater1.DataBind();
    }

    protected void Repeater1_ItemDataBound(object sender, RepeaterItemEventArgs e)
    {
        if (e.Item.ItemType == ListItemType.Item ||
                e.Item.ItemType == ListItemType.AlternatingItem)
        {
            DropDownList cboHotel = (DropDownList)e.Item.FindControl("cboHotel");
            cboHotel.DataSource = rstHotels;
            cboHotel.DataBind();
            cboHotel.Items.Insert(0, new ListItem("Please select", "0"));

            DataRowView rowData = (DataRowView)e.Item.DataItem;  // current binding row data
            if (rowData["Hotel_id"] != DBNull.Value)
                cboHotel.SelectedValue = rowData["Hotel_id"].ToString();
        }
    }

    protected void cboHotel_SelectedIndexChanged(object sender, EventArgs e)
    {
        DropDownList cboHotel = (DropDownList)sender;
        RepeaterItem rptRow = (RepeaterItem)cboHotel.NamingContainer;

        string sInfo = "";
        sInfo += $"Row index selected = {rptRow.ItemIndex}\n";

        Label lblPerson = (Label)rptRow.FindControl("lblPerson");
        sInfo += $"Row person information = {lblPerson.Text}\n";
        sInfo += $"combo Hotel id = {cboHotel.SelectedItem.Value}\n";
        sInfo += $"combo Hotel Name (text) = {cboHotel.SelectedItem.Text}\n";

        txtInfo.Text = sInfo;   
    }

And the result is now this:

enter image description here

So, often it can be ok to place some data source in the markup, but the instant you have post backs and additional code logic, then I suggest you "take control" of the data binding by using code behind, and not leave up when re-binding may, or may not reoccur.