Monday, April 11, 2011

Can I databind (2-way) dynamically generated RadioButtonLists?

I am trying to create a dynamic survey page. The idea seems simple, but trying to implement it in ASP.NET is getting me very frustrated...

Each user is linked to an individual list of (multiple-choice) questions. I have the following data tables:

Surveys:

User   | Question   | Rank
-------+------------+-----
user-x | question-x |    1
user-x | question-y |    2
user-y | question-z |    1
user-y | question-x |    2

Questions:

ID         | Text | Choices
-----------+------+-----------------------
question-x | Foo? | yes|no
question-y | Bar? | never|sometimes|always
question-z | Baz? | 1|2|3|4|5|6|7|8|9|10

Answers:

User   | Question   | Answer
-------+------------+-------
user-x | question-x |    0
user-x | question-y |    2
user-y | question-z |    5

So the answer choices are character-separated strings that need to be expended when databinding, and the answers are stored as a 0-based index to the answer. (So user-x answered "always" on question-y.)

Here is my first version of the code:

<asp:SqlDataSource ID="SqlDataSource1" runat="server" 
    ConnectionString="<%$ ConnectionStrings:dbconn %>" 
    SelectCommand="
     SELECT Questions.Text AS Question, Questions.Choices, Answers.Answer 
     FROM Questions 
      INNER JOIN Surveys ON Surveys.Question = Questions.ID 
      LEFT OUTER JOIN Answers ON Questions.ID = Answers.Question 
       AND Users.ID = Answers.User 
     WHERE (Surveys.User = @User) 
     ORDER BY Surveys.Rank">
    <SelectParameters>
     <asp:QueryStringParameter Name="User" QueryStringField="id" />
    </SelectParameters>
</asp:SqlDataSource>

<asp:Repeater ID="Repeater1" runat="server" DataSourceID="SqlDataSource1" 
    onitemdatabound="Repeater1_ItemDataBound">
    <HeaderTemplate><table></HeaderTemplate>
    <ItemTemplate>
     <tr>
      <td><%# Eval("Question") %></td>
      <td><asp:Label runat="server" ID="ChoicesLabel"/></td>
     </tr>
    </ItemTemplate>
    <FooterTemplate></table></FooterTemplate>
</asp:Repeater>

<asp:Button ID="Button1" runat="server" Text="Submit" onclick="Button1_Click"/>

and:

protected void Repeater1_ItemDataBound(object sender, RepeaterItemEventArgs e)
{
    if (e.Item.ItemType != ListItemType.Item && e.Item.ItemType != ListItemType.AlternatingItem) 
     return;

    DataRowView row = (DataRowView)e.Item.DataItem;
    RadioButtonList rbl = new RadioButtonList();
    rbl.RepeatDirection = RepeatDirection.Horizontal;
    string[] Choices = row["Choices"].ToString().Split('|');
    for (int n = 0; n < Choices.Length; n++)
    {
     rbl.Items.Add(new ListItem(Choices[n], n.ToString()));
    }
    if (row["Answer"] != DBNull.Value)
     rbl.SelectedIndex = (int)row["Answer"];
    ((Label)e.Item.FindControl("ChoicesLabel")).Controls.Add(rbl);
}

protected void Button1_Click(object sender, EventArgs e)
{
}

Now, the first problem was that after I click "Submit", I get a page in return which only contains the questions, not the radiobuttons with answers! After lots of searching, I fixed this by forcing the data binding to occur on page initialization:

public void Page_Init(Object sender, EventArgs e)
{
    Repeater1.DataBind();
}

However, I am still completely in the dark if it is possible to do a 2-way databinding on the RadioButtonLists? (I mean with a <%# Bind() %> command.) Or do I have to write my own procedure for submitting the answers back to the database? To make things more complicated, on first visit the answers will have to be INSERTed into the Answers table, while on return visits the existing rows can be UPDATEd.

From stackoverflow
  • Yes, I have found a similar problem when I want to use Bind with the DropDownList. What I do is create a custom control, from your example, which inherits from a RadioButtonList. I give it one extra property called Value. When this is set I then code to set the selected item through the values. When I call the get I return the actual selected value so in essence you need a custom class which inherits from the RadioButtonList and give it an extra property

        using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Web;
    using System.Web.UI.WebControls;
    
    /// <summary>
    /// Summary description for BindingRadioButtonList
    /// </summary>
    public class BindingRadioButtonList : RadioButtonList
    {
        public string Value
        {
            get
            {
                return this.SelectedValue;
            }
            set
            {
                if (this.Items.FindByValue(value) != null)
                {
                    this.ClearSelection();
                    this.Items.FindByValue(value).Selected = true;
                }
            }
        }
    
        public BindingRadioButtonList()
        {
         //
         // TODO: Add constructor logic here
         //
        }
    }
    

    I can then easily use this:

    <cc1:BindingRadioButtonList ID="a1" runat="server" Value='<%#Bind("YourValue")%>'>
    </cc1:BindingRadioButtonList>
    

    Andrew

    paulwhit : Trying to figure out how to consume this. Do I need to compile as a DLL and add it to my toolbox?

0 comments:

Post a Comment