views:

121

answers:

1

I'm a tad confused. I have a Repeater which populates a list of Checkbox controls and Label controls. But it doesn't seem like the checked state of the checkboxes is remembered in ViewState until AFTER the first postback.

Scenario: I have 5 items in my custom checkbox list. I select the first 3 and submit the form. The first 3 are no longer selected. I select items 1, 3 and 5 and submit again. After the page is loaded 1, 3 and 5 are still selected.

Here's the entire code for the test page I'm using. My apologies for the VB :-p

Imports System.Xml

Partial Public Class _Default
    Inherits System.Web.UI.Page

    Dim _roles As Repeater
    Dim _output As Literal

    Private Sub Page_Init(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Init
        _roles = New Repeater()
        _roles.ItemTemplate = New RolesListTemplate(ListItemType.Item)
        Me.Form.Controls.Add(_roles)

        Dim btn As New Button()
        btn.Text = "Save"
        btn.UseSubmitBehavior = True
        AddHandler btn.Command, AddressOf btnSave_OnCommand
        Me.Form.Controls.Add(btn)

        _output = New Literal
        Me.Form.Controls.Add(_output)
    End Sub

    Private Sub btnSave_OnCommand(ByVal sender As Object, ByVal e As CommandEventArgs)
        Dim roleData As String = GetXML(_roles)
        _output.Text = "<br>" & HttpUtility.HtmlEncode(roleData) & "<br>DONE"
    End Sub

    Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load

        If Not Page.IsPostBack Then
            Dim RoleData As New List(Of Role)

            RoleData.Add(New Role With {.selected = 0, .enabled_ind = 1, .role_id = 123, .role_description = "Role 123"})
            RoleData.Add(New Role With {.selected = 0, .enabled_ind = 1, .role_id = 345, .role_description = "Role 345"})
            RoleData.Add(New Role With {.selected = 0, .enabled_ind = 1, .role_id = 678, .role_description = "Role 678"})
            RoleData.Add(New Role With {.selected = 0, .enabled_ind = 1, .role_id = 6987, .role_description = "Role 6987"})
            RoleData.Add(New Role With {.selected = 0, .enabled_ind = 1, .role_id = 1122, .role_description = "Role 1122"})

            If RoleData IsNot Nothing Then
                If RoleData.Count > 0 Then
                    _roles.DataSource = RoleData
                    _roles.DataBind()
                    _roles.Visible = True
                Else
                    _roles.Visible = False
                End If
            End If
        End If

    End Sub

    Private Function GetXML(ByVal _cb_roles As Repeater) As String

        Dim settings As New XmlWriterSettings
        settings.CheckCharacters = True
        settings.CloseOutput = True
        settings.OmitXmlDeclaration = True

        Dim xw As XmlWriter
        Dim sb As New StringBuilder()
        xw = XmlWriter.Create(sb, settings)

        xw.WriteStartDocument()
        xw.WriteStartElement("roles")

        For Each row As RepeaterItem In _cb_roles.Items
            Dim pnl As Panel = TryCast(row.Controls.Item(0), Panel)
            Dim cb As CheckBox = TryCast(pnl.Controls.Item(0), CheckBox)
            Dim id As String = String.Empty
            Dim parts() As String = cb.ID.Split("_"c)
            id = cb.InputAttributes("role_id")
            xw.WriteStartElement("role")
            xw.WriteAttributeString("role_id", id)
            If cb.Checked Then
                xw.WriteAttributeString("selected", "1")
            Else
                xw.WriteAttributeString("selected", "0")
            End If
            xw.WriteEndElement()

        Next

        xw.WriteEndElement()
        xw.WriteEndDocument()
        xw.Close()

        Return sb.ToString
    End Function

    Public Class RolesListTemplate
        Implements ITemplate, INamingContainer

        Private _ltItemType As ListItemType
        Private _ctlParent As WebControl

        Sub New(ByVal pType As ListItemType)
            Try
                _ltItemType = pType
            Catch ex As Exception
                Throw ex
            End Try
        End Sub

        Public Sub InstantiateIn(ByVal container As System.Web.UI.Control) Implements System.Web.UI.ITemplate.InstantiateIn

            Select Case _ltItemType

                Case ListItemType.Header
                Case ListItemType.Item

                    Dim pnl As New Panel()

                    Dim cb As New CheckBox()
                    cb.AutoPostBack = True
                    cb.ID = "role_check"
                    AddHandler cb.DataBinding, AddressOf RolesListTemplate_DataBind
                    pnl.Controls.Add(cb)

                    Dim lbl As New Label()
                    lbl.ID = "role_lbl"
                    AddHandler lbl.DataBinding, AddressOf RolesListTemplate_DataBind
                    pnl.Controls.Add(lbl)

                    container.Controls.Add(pnl)

                Case ListItemType.AlternatingItem
                Case ListItemType.Footer

            End Select

        End Sub

        Private Sub RolesListTemplate_DataBind(ByVal sender As Object, ByVal e As System.EventArgs)

            Dim _sender As Control = DirectCast(sender, Control)
            Dim container As RepeaterItem = DirectCast(_sender.NamingContainer, RepeaterItem)

            Dim role As Role = TryCast(container.DataItem, Role)
            If role IsNot Nothing Then

                If role.selected OrElse role.enabled_ind Then
                    Select Case _sender.ID
                        Case "role_check"
                            Dim role_check As CheckBox = DirectCast(_sender, CheckBox)
                            role_check.Checked = role.selected
                            role_check.ID = "role_list_" & role.role_id.ToString()
                            role_check.InputAttributes("role_id") = role.role_id.ToString()

                        Case "role_lbl"
                            Dim role_lbl As Label = DirectCast(_sender, Label)
                            role_lbl.Text = role.role_description
                    End Select
                Else
                    container.Visible = False
                End If

            End If

        End Sub

    End Class

    Public Class Role
        Public enabled_ind As Boolean
        Public selected As Boolean
        Public role_id As Integer
        Public role_description As String
    End Class

End Class
+2  A: 

This is a very common mistake. You want to call DataBind in the Init event, even for post backs.

ChaosPandion
Aah... so, I have to make my database calls on ever page load? It's a low traffic page, so the database impact is minimal, but seems kinda sloppy to me.
Wes P
@Wes - It is unfortunate but Web Forms has always been about rapid development over efficiency.
ChaosPandion
Dynamic controls are a bit sloppy. Chances are that you can restructure your code to bind the data to a repeater on the page instead of adding the repeater to a container via code.
ScottE
@ScottE - While I agree in principle, in practice these CRUD type pages work just fine with the dynamic controls.
ChaosPandion