Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do you get values from dynamic controls in a ListView on postback?

I've recently had to go back to dealing with webforms code and have run into an issue trying to update an existing page that shows a list of survey responses.

I have a ListView showing details of people who have answered a survey. When you click on an icon in the row that row enters edit mode and shows their information (name, email etc) as input boxes.

So far so good, now I need to add the questions and the answers for that survey and that person. Writing them out is easy, but when the postback happens the row goes back to the ItemTemplate and the controls are gone.

I know with dynamic controls you are supposed to create the in Page_Init so webforms can rebind them, but here the controls aren't created until the ListItem ItemEditing event, and the data saved in the ItemUpdating event.

I'm stuck on how I could make the controls in the right place with the right data at the right point in the lifecycle. Right now I'm trying to pull the values from Request.Form but with a CheckboxList it's hard to figure out what has been selected.

Edit: New example that should actually run

<%@ Page Language="C#" AutoEventWireup="true" CodeFile="QuestionExample.aspx.cs" Inherits="QuestionExample" %>

<!DOCTYPE html>

<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
    <title></title>
</head>
<body>
    <form id="form1" runat="server">
                <table>
        <asp:ListView runat="server" ID="LV" OnItemEditing="LV_OnItemEditing" OnItemUpdating="LV_OnItemUpdating">
            <LayoutTemplate>
                    <tr>
                        <td><asp:PlaceHolder runat="server" ID="itemPlaceHolder"></asp:PlaceHolder></td>
                    </tr>       
            </LayoutTemplate>
            <ItemTemplate>
                <asp:LinkButton ID="EditButton" runat="server" CommandName="Edit" Text="Edit"/>
                ID: <asp:Label runat="server" ID="lblLeadID" Text="<%# ((Lead)Container.DataItem).LeadID %>" /> Name: <%# ((Lead)Container.DataItem).Name %><br/>                
            </ItemTemplate>
            <EditItemTemplate>
                <asp:LinkButton ID="UpdateButton" runat="server" CommandName="Update" Text="Update" />
                ID: <asp:Label runat="server" ID="lblLeadID" Text="<%# ((Lead)Container.DataItem).LeadID %>" /> <asp:Label runat="server">Name</asp:Label>
                <asp:TextBox runat="server" id="tbName" Text="<%# ((Lead)Container.DataItem).Name %>"></asp:TextBox>
                <asp:Panel runat="server" id="pnlQuestions"></asp:Panel>
            </EditItemTemplate>
        </asp:ListView>
         </table>
    </form>
</body>
</html>

Then the code behind:

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

public partial class QuestionExample : Page
{
    #region fake data

    protected List<Question> Questions = new List<Question>()
    {
        new Question { QuestionID = 0, QuestionType = QuestionType.Textbox, QuestionText = "TextBox", Options = null },
        new Question { QuestionID = 1, QuestionType = QuestionType.DropDownList, QuestionText = "DDL", Options = new List<string> { "A", "B", "C"} },
    };

    protected List<Lead> Leads = new List<Lead>
    {
        new Lead { LeadID = 0, Name = "Bob", Answers = new Dictionary<string, string> { { "TextBox", "Hi" }, { "DDL", "B" } } },
        new Lead { LeadID = 1, Name = "Fred", Answers = new Dictionary<string, string> { { "TextBox", "Stuff" }, { "DDL", "C" } } },
    };

    #endregion    

    protected void Page_Load(object sender, EventArgs e)
    {
        LV.DataSource = Leads;
        LV.DataBind();
    }

    protected void LV_OnItemEditing(object sender, ListViewEditEventArgs e)
    {
        LV.EditIndex = e.NewEditIndex;
        LV.DataBind();
        var leadLabel = (Label)LV.Items[e.NewEditIndex].FindControl("lblLeadID");
        var leadID = int.Parse(leadLabel.Text);
        var panel = (Panel)LV.Items[e.NewEditIndex].FindControl("pnlQuestions");
        var lead = Leads.First(l => l.LeadID == leadID);
        foreach (var answer in lead.Answers)
        {
            var question = Questions.First(q => q.QuestionText == answer.Key);
            panel.Controls.Add(CreatQuestionControl(question, lead, true));
        }
    }

    protected Control CreatQuestionControl(Question question, Lead lead, bool setAnswers)
    {
        Control result = null;
        switch (question.QuestionType)
        {
            case QuestionType.Textbox:
                var tb = new TextBox();
                if (setAnswers)
                {
                    var answer = lead.Answers[question.QuestionText];
                    tb.Text = answer;
                }
                result = tb;
                break;
            case QuestionType.DropDownList:
                var ddl = new DropDownList { DataSource = question.Options };
                ddl.DataBind();
                if (setAnswers)
                {
                    var answer = lead.Answers[question.QuestionText];
                    ddl.SelectedValue = answer;
                }
                result = ddl;
                break;
        }

        return result;
    }

    protected void LV_OnItemUpdating(object sender, ListViewUpdateEventArgs e)
    {
        // Get input data here somehow

        LV.EditIndex = -1;
    }
}

public class Lead
{
    public int LeadID { get; set; }
    public string Name { get; set; }
    public Dictionary<string, string> Answers { get; set; }
}

public class Question
{
    public int QuestionID { get; set; }
    public string QuestionText { get; set; }
    public QuestionType QuestionType { get; set; }
    public List<string> Options { get; set; }
}

public enum QuestionType
{
    Textbox = 0,
    DropDownList = 1,
}
like image 244
Mant101 Avatar asked Jun 11 '15 15:06

Mant101


1 Answers

Without seeing your code it's difficult to give a complete answer, but basically you need to recreate the control tree, including the dynamically created controls, on postback.

You can do this, for example, in the Page_Load event handler.

You can store any data you need to recreate the control tree in ViewState so that it is available on PostBack.

I'm not sure what you mean by:

... when the postback happens the row goes back to the ItemTemplate

I would expect EditIndex to stay unchanged after a postback, unless you explicitly modify it, and therefore the edit row to be unchanged, and EditItemTemplate to be used.

If you can post a simplified sample that illustrates your problem I (or someone else) may be able to help further.

UPDATE

Based on the posted code, you need to recreate the dynamic controls in the ItemUpdating event handler and add them to the control tree in the same order as you created them in the ItemEditing event handler. I.e. you probably need to repeat the code:

var questionPannel = lL.Items[e.ItemIndex].FindControl("lead_table") as HtmlTable;
var campaignQuestionsTableRow = questionPannel.FindControl("campaign_questions") as HtmlTableRow;

var questions = GetQuestions();

foreach(var question in questions)
{
    // This builds a WebControl for the dynamic question
    var control = CreateQuestionControl(question);
    campaignQuestionsTableRow.AddControl(control); 
}

UPDATE 2

When updating, you need to recreate the dynamic controls on PostBack. Change your Page_Load event handler to something like:

protected void Page_Load(object sender, EventArgs e)
{
    LV.DataSource = Leads;
    LV.DataBind();
    if (IsPostBack)
    {
        if (LV.EditIndex >= 0)
        {
            var leadLabel = (Label)LV.Items[LV.EditIndex].FindControl("lblLeadID");
            var leadID = int.Parse(leadLabel.Text);
            var panel = (Panel)LV.Items[LV.EditIndex].FindControl("pnlQuestions");
            var lead = Leads.First(l => l.LeadID == leadID);
            foreach (var answer in lead.Answers)
            {
                var question = Questions.First(q => q.QuestionText == answer.Key);
                panel.Controls.Add(CreatQuestionControl(question, lead, true));
            }

        }
    }
}

On the first Postback (click on Edit), EditIndex will be -1, and the dynamic controls are created in the OnItemEditing handler.

On the second Postback (click on Update), EditIndex will be the index of the line you're editing, and you need to recreate the dynamic controls in Page_Load. If you do this, you'll find your posted values when you get to the OnItemUpdating event handler.

While I think it's worthwhile understanding how to use dynamic controls in this way, in a production app I'd probably use a Repeater inside the EditItemTemplate as suggested by Elisheva Wasserman. The Repeater could contain a UserControl that encapsulates the logic to hide/show UI elements based on the question type.

like image 80
Joe Avatar answered Oct 04 '22 04:10

Joe