Client-side ECMAScript (JScript, JavaScript) can be used to track client-side state changes in a control that is not a form element. Such a control does not have a way to post data back to the server through form submission. Performing an event postback is possible but might not be meaningful. This problem can be solved by using hidden input fields that carry the data for the control. The control must emit the hidden input fields and emit script that injects state information into the hidden fields at some point before the form submits. When the control is loaded, the data from the hidden fields can be retrieved and used by the control. To enable this mechanism, a control can invoke the RegisterHiddenField method of its Page to emit a hidden field and implement the IPostBackDataHandler interface to recover the value of the hidden field and update its properties.
The following sample demonstrates this scenario. The control in the sample (DHtmlControl) emits a span whose color changes on the client when it is selected. The control emits a hidden variable whose value is set to a Boolean variable by client-side script to indicate whether the control was selected. The control exposes a Boolean property (Selected) that indicates whether the control was selected on the client. The control implements the IPostBackDataHandler interface, so that when the page is posted to the server, the control can read the value of the hidden field and update its Selected property. The control also exposes an event (SelectedChanged) that it raises if the control is selected on the client.
To build the sample, see the instructions in Server Control Samples.
using System;
using System.Collections;
using System.Collections.Specialized;
using System.Drawing;
using System.Web;
using System.Web.UI;
namespace CustomControls
{
public class DHtmlControl : Control, IPostBackDataHandler
{
public event EventHandler SelectedChanged;
public string Text
{
get
{
object obj = ViewState["Text"];
return (obj == null) ? String.Empty : (string)obj;
}
set
{
ViewState["Text"] = value;
}
}
public bool Selected
{
get
{
object obj = ViewState["Selected"];
return (obj == null) ? false : (bool)obj;
}
set
{
ViewState["Selected"] = value;
}
}
protected string HelperID
{
get
{
return "__" + ClientID + "_State";
}
}
protected override void OnInit(EventArgs e)
{
base.OnInit(e);
if (Page != null)
{
Page.RegisterRequiresPostBack(this);
}
}
protected override void OnPreRender(EventArgs e)
{
base.OnPreRender(e);
if (Page != null)
{
Page.RegisterHiddenField(HelperID, Selected.ToString());
}
}
protected override void Render(HtmlTextWriter writer)
{
string postback = "";
if (Page != null)
{
postback = Page.GetPostBackEventReference(this) + ";";
}
string click = "onclick=\"var sel=getAttribute('selected'); sel = (sel.toLowerCase() == 'true'); sel=!sel; setAttribute('selected', sel.toString());this.style.backgroundColor=sel?'red':'white';" + HelperID +".value=sel.toString();" + postback + "\"";
string style = "style=\"cursor:hand;background-color:" + (Selected ? "red" : "white") + "\"";
string selected = "selected=\"" + Selected.ToString() + "\"";
writer.Write("<span " + style + " " + click + " " + selected + ">" + Text + "</span>");
}
bool IPostBackDataHandler.LoadPostData(string postDataKey, NameValueCollection postCollection)
{
string value = postCollection[HelperID];
if (value != null)
{
bool newValue = (String.Compare(value, "true", true) == 0);
bool oldValue = Selected;
Selected = newValue;
// If there is a change, raise a change event.
return (newValue != oldValue);
}
return false;
}
void IPostBackDataHandler.RaisePostDataChangedEvent()
{
// There was a change, so raise any events.
if (SelectedChanged != null)
{
SelectedChanged(this, EventArgs.Empty);
}
}
}
}
[Visual Basic]
Option Explicit
Option Strict
Imports System
Imports System.Collections
Imports System.Collections.Specialized
Imports System.Drawing
Imports System.Web
Imports System.Web.UI
Imports Microsoft.VisualBasic
Namespace CustomControls
Public Class DHtmlControl
Inherits Control
Implements IPostBackDataHandler
Public Event SelectedChanged As EventHandler
Public Property Text() As String
Get
Dim obj As Object = ViewState("Text")
If obj Is Nothing Then
Return String.Empty
Else
Return CStr(obj)
End If
End Get
Set
ViewState("Text") = value
End Set
End Property
Public Property Selected() As Boolean
Get
Dim obj As Object = ViewState("Selected")
If obj Is Nothing Then
Return False
Else
Return CBool(obj)
End If
End Get
Set
ViewState("Selected") = value
End Set
End Property
Protected ReadOnly Property HelperID() As String
Get
Return "__" & ClientID & "_State"
End Get
End Property
Protected Overrides Sub OnInit(e As EventArgs)
MyBase.OnInit(e)
If Not (Page Is Nothing) Then
Page.RegisterRequiresPostBack(Me)
End If
End Sub
Protected Overrides Sub OnPreRender(e As EventArgs)
MyBase.OnPreRender(e)
If Not (Page Is Nothing) Then
Page.RegisterHiddenField(HelperID, Selected.ToString())
End If
End Sub
Protected Overrides Sub Render(writer As HtmlTextWriter)
Dim postback As String = ""
If Not (Page Is Nothing) Then
postback = Page.GetPostBackEventReference(Me) & ";"
End If
Dim click As String = "onclick=""var sel=getAttribute('selected'); sel = (sel.toLowerCase() == 'true'); sel=!sel; setAttribute('selected', sel.toString());this.style.backgroundColor=sel?'red':'white';" & HelperID & ".value=sel.toString();" & postback & """"
Dim style As String = "style=""cursor:hand;background-color:" & IIf(Selected, "red", "white").ToString() & """"
Dim selectedPiece As String = "selected=""" & Selected.ToString() & """"
writer.Write(("<span " & style & " " & click & " " & selectedPiece & ">" & Text & "</span>"))
End Sub
Function LoadPostData(postDataKey As String, postCollection As NameValueCollection) As Boolean Implements IPostBackDataHandler.LoadPostData
Dim value As String = postCollection(HelperID)
If Not (value Is Nothing) Then
Dim newValue As Boolean = String.Compare(value, "true", True) = 0
Dim oldValue As Boolean = Selected
Selected = newValue
' If there is a change, raise a change event.
Return newValue <> oldValue
End If
Return False
End Function
Sub RaisePostDataChangedEvent() Implements IPostBackDataHandler.RaisePostDataChangedEvent
' There was a change, so raise any events.
RaiseEvent SelectedChanged(Me, EventArgs.Empty)
End Sub
End Class
End Namespace
Page that uses the DHTML control
The following page uses the DHTML control and attaches an event handler to its SelectedChanged event. Note that even though the control is within form tags it is not a form element and does not generate a name/value pair that can be posted to the server.
<%@Register TagPrefix="Custom" NameSpace="CustomControls" Assembly="CustomControls" %>
<script language="C#" runat = "server" >
private void SelectedChangedHandler(object sender, EventArgs e)
{
label.Text = "You selected the DHTML control";
}
</script>
<html>
<body>
<form id="Form1" method="post" runat="server">
<Custom:DHtmlControl OnSelectedChanged = "SelectedChangedHandler" runat="server" Text="SelectMe" />
<br>
<asp:Label id = "label" runat = "server" />
</form>
</body>
</html>
[Visual Basic]
<%@Register TagPrefix="Custom" NameSpace="CustomControls" Assembly="CustomControls" %>
<script language="VB" runat = "server" >
Private Sub SelectedChangedHandler(sender As Object, e As EventArgs)
label.Text = "You selected the DHTML control"
End Sub
</script>
<html>
<body>
<form id="Form1" method="post" runat="server">
<Custom:DHtmlControl OnSelectedChanged = "SelectedChangedHandler" runat="server" Text="SelectMe" />
<br>
<asp:Label id = "label" runat = "server" />
</form>
</body>
</html>
See Also
Generating Client-Side Script for Postback