Friday, April 14, 2006

Mutually exclusive RadioButton in INamingContainer redux

Some time back I'd posted about the bug with mutually excusive radiobuttons in repeater controls for VB.Net and hacking my way around the bug. Well, since some time I'd been thinking of posting how I actually did it for the benefit of others and today is the day! As they say, better late than never. Here's the CustomRadioButton.vb class:



Imports System
Imports System.Web.UI
Imports System.Web.UI.WebControls
Imports System.Globalization
Imports System.Collections.Specialized

Public Class CustomRadioButtonControl
Inherits WebControl
Implements IPostBackDataHandler

Shared Sub New()
CustomRadioButtonControl.EventCheckedChanged = New Object
End Sub

Public Sub New()
MyBase.New(HtmlTextWriterTag.Input)
End Sub
#Region "Properties"

Public Event CheckedChanged As EventHandler
Private Shared ReadOnly EventCheckedChanged As Object

Private ReadOnly Property UniqueGroupName() As String
Get
Dim grpName As String = Me.GroupName
Dim unqId As String = Me.UniqueID
Dim num As Integer = unqId.LastIndexOf(":"c)
If (num >= 0) Then
grpName = (unqId.Substring(0, (num + 1)) & grpName)
End If
Return grpName
End Get
End Property


Public Overridable Property GroupName() As String
Get
Dim grpName As String = CType(Me.ViewState.Item("GroupName"), String)
If (Not grpName Is Nothing) Then
Return grpName
End If
Return String.Empty
End Get
Set(ByVal value As String)
Me.ViewState.Item("GroupName") = value
End Set
End Property

Public Overridable Property Checked() As Boolean
Get
Dim grpChecked As Object = Me.ViewState.Item("Checked")
If (Not grpChecked Is Nothing) Then
Return CType(grpChecked, Boolean)
End If
Return False
End Get
Set(ByVal value As Boolean)
Me.ViewState.Item("Checked") = value
End Set
End Property

Private ReadOnly Property SaveCheckedViewState() As Boolean
Get
If ((Not MyBase.Events.Item(Me.EventCheckedChanged) Is Nothing) OrElse Not Me.Enabled) Then
Return True
End If
Dim baseType As Type = MyBase.GetType
If ((Not baseType Is GetType(CheckBox)) AndAlso (Not baseType Is GetType(RadioButton))) Then
Return True
End If
Return False
End Get
End Property

Public Overridable Property AutoPostBack() As Boolean
Get
Dim doAutoPostBack As Object = Me.ViewState.Item("AutoPostBack")
If (Not doAutoPostBack Is Nothing) Then
Return CType(doAutoPostBack, Boolean)
End If
Return False
End Get
Set(ByVal value As Boolean)
Me.ViewState.Item("AutoPostBack") = value
End Set
End Property


Private ReadOnly Property Value() As String
Get
Dim val As String = Attributes("value")
If (val Is Nothing) Then
val = UniqueID
End If
Return val
End Get
End Property

Public Overridable Property [Text]() As String
Get
Dim grpText As String = CType(Me.ViewState.Item("Text"), String)
If (Not grpText Is Nothing) Then
Return grpText
End If
Return String.Empty
End Get
Set(ByVal value As String)
Me.ViewState.Item("Text") = value
End Set
End Property

Public Overridable Property TextAlign() As TextAlign
Get
Dim align As Object = Me.ViewState.Item("TextAlign")
If (Not align Is Nothing) Then
Return CType(align, TextAlign)
End If
Return TextAlign.Right
End Get
Set(ByVal value As TextAlign)
If ((value < TextAlign.Left) OrElse (value > TextAlign.Right)) Then
Throw New ArgumentOutOfRangeException("value")
End If
Me.ViewState.Item("TextAlign") = value
End Set
End Property

#End Region

Protected Overrides Sub Render(ByVal writer As HtmlTextWriter)
If (Not Me.Page Is Nothing) Then
Me.Page.VerifyRenderingInServerForm(Me)
End If
Dim doRender As Boolean = False

If Not Me.Enabled Then
writer.AddAttribute(HtmlTextWriterAttribute.Disabled, "disabled")
doRender = True
End If
Dim tip As String = Me.ToolTip
If (tip.Length > 0) Then
writer.AddAttribute(HtmlTextWriterAttribute.Title, tip)
doRender = True
End If
Dim _onClick As String = Nothing
Dim _attributes As AttributeCollection = MyBase.Attributes
Dim _value As String = _attributes.Item("value")
If (Not _value Is Nothing) Then
_attributes.Remove("value")
End If
_onClick = _attributes.Item("onclick")
If (Not _onClick Is Nothing) Then
_attributes.Remove("onclick")
End If
If (_attributes.Count <> 0) Then
_attributes.AddAttributes(writer)
doRender = True
End If
If (Not _value Is Nothing) Then
_attributes.Item("value") = _value
End If
If doRender Then
writer.RenderBeginTag(HtmlTextWriterTag.Span)
End If
Dim _text As String = Me.Text
Dim _clientId As String = Me.ClientID
If (_text.Length <> 0) Then
If (Me.TextAlign = TextAlign.Left) Then
Me.RenderLabel(writer, _text, _clientId)
Me.RenderInputTag(writer, _clientId, _onClick)
Else
Me.RenderInputTag(writer, _clientId, _onClick)
Me.RenderLabel(writer, _text, _clientId)
End If
Else
Me.RenderInputTag(writer, _clientId, _onClick)
End If
If doRender Then
writer.RenderEndTag()
End If

End Sub

Private Sub RenderLabel(ByVal writer As HtmlTextWriter, ByVal [text] As String, ByVal clientID As String)
writer.AddAttribute(HtmlTextWriterAttribute.For, clientID)
writer.RenderBeginTag(HtmlTextWriterTag.Label)
writer.Write(text)
writer.RenderEndTag()
End Sub


#Region "Rendering"

Protected Overrides Sub OnPreRender(ByVal e As EventArgs)
If ((Not Me.Page Is Nothing) AndAlso Me.Enabled) Then
Me.Page.RegisterRequiresPostBack(Me)
End If
If Not Me.SaveCheckedViewState Then
Me.ViewState.SetItemDirty("Checked", False)
End If

If (((Not Me.Page Is Nothing) AndAlso Not Me.Checked) AndAlso Me.Enabled) Then
Me.Page.RegisterRequiresPostBack(Me)
End If
If (Me.GroupName.Length = 0) Then
Me.GroupName = Me.UniqueID
End If
End Sub

Friend Sub RenderInputTag(ByVal writer As HtmlTextWriter, ByVal clientID As String, ByVal onClick As String)
writer.AddAttribute(HtmlTextWriterAttribute.Id, clientID)
writer.AddAttribute(HtmlTextWriterAttribute.Type, "radio")
writer.AddAttribute(HtmlTextWriterAttribute.Name, Me.GroupName)
writer.AddAttribute(HtmlTextWriterAttribute.Value, Me.Value)
If Me.Checked Then
writer.AddAttribute(HtmlTextWriterAttribute.Checked, "checked")
End If
If Not Me.Enabled Then
writer.AddAttribute(HtmlTextWriterAttribute.Disabled, "disabled")
End If
If Me.AutoPostBack Then
If (Not onClick Is Nothing) Then
onClick = (onClick & Me.Page.GetPostBackClientEvent(Me, ""))
Else
onClick = Me.Page.GetPostBackClientEvent(Me, "")
End If
writer.AddAttribute(HtmlTextWriterAttribute.Onclick, onClick)
writer.AddAttribute("language", "javascript")
Else
If (Not onClick Is Nothing) Then
writer.AddAttribute(HtmlTextWriterAttribute.Onclick, onClick)
End If
End If
If (Me.AccessKey.Length > 0) Then
writer.AddAttribute(HtmlTextWriterAttribute.Accesskey, Me.AccessKey)
End If
If (Me.TabIndex <> 0) Then
writer.AddAttribute(HtmlTextWriterAttribute.Tabindex, Me.TabIndex.ToString(NumberFormatInfo.InvariantInfo))
End If
writer.RenderBeginTag(HtmlTextWriterTag.Input)
writer.RenderEndTag()
End Sub

Protected Overridable Sub OnCheckedChanged(ByVal e As EventArgs)
Dim handler As EventHandler = CType(MyBase.Events.Item(CustomRadioButtonControl.EventCheckedChanged), EventHandler)
If (Not handler Is Nothing) Then
handler.Invoke(Me, e)
End If
End Sub

#End Region

#Region "IPostBackDataHandler Members"

Public Overridable Shadows Sub RaisePostDataChangedEvent() Implements IPostBackDataHandler.RaisePostDataChangedEvent
OnCheckedChanged(EventArgs.Empty)
End Sub

Private Function LoadPostData(ByVal postDataKey As String, ByVal postCollection As NameValueCollection) As Boolean Implements IPostBackDataHandler.LoadPostData
Dim grpName As String = postCollection.Item(Me.GroupName)
Dim flag As Boolean = False
If ((Not grpName Is Nothing) AndAlso grpName.Equals(Me.Value)) Then
If Not Me.Checked Then
Me.Checked = True
flag = True
End If
Return flag
End If
If Me.Checked Then
Me.Checked = False
End If
Return flag
End Function

#End Region
End Class

Embedding the control in your aspx page is simple, just register the tag prefix using:
<%@ Register TagPrefix="ucl" Namespace="XYZ.Website" Assembly="XYZ.Website"%>

PS: forget about how to showing the code snippet to embed it aspx page, blogger really sucks when you want to include html tags as is within your post. Basically you set the groupName property and the id property!

3 comments:

  1. wont > and < help ?
    -Avi

    ReplyDelete
  2. damn! i meant using "& g t ;" and "& l t ;" (strip quotes and spaces"

    ReplyDelete
  3. I tried & lt; and & gt; but atleast in preview they looked quite borked! Other thing that I tried was the "xmp" tag but then blogger messes up the "Convert linebreaks to br" setting and since there is no per-post setting to turn this feature on/off I felt the best way is to not post the e.g. at all! I had the same issues with the xslt on one of my previous posts, where I atleast got it fixed on the site but it would break the RSS feed.
    Btw, glad that you're atleast taking some time off from your work life and reading blogs :). I have been waiting patiently for a blog post from you & you better turn off comments on your blogs for now, you're getting spammed like crazy.

    S

    ReplyDelete