An interesting question came up on the ASP.NET forums asking why a TextBox which has its value changed by client-side Javascript persists those changes across postbacks, while a Label does not. And in a nut shell, this question covered two of the biggest causes of confusion among newcomers to ASP.NET: the difference between ViewState and IPostBackDataHandler; and the difference between client-side operations and server-side operations.
First a look at the code that was posted by the questioner:
[ASPX]1 <% @ Page Language="VB" AutoEventWireup="false"
2 CodeFile="ButtonClickWithJavascript.aspx.vb"
3 Inherits="ButtonClickWithJavascript" %>
4 <! DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "
5 http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd" >
6 < html xmlns ="http://www.w3.org/1999/xhtml" >
7 < head runat ="server" >
8 < title > Untitled Page </ title >
9 < script language ="javascript" >
10function btchange() {
11document.getElementById("Label1").innerText="Javascript changed!";
12document.getElementById("TextBox1").value="Text from Javascript!";
13}
14 </ script >
15 </ head >
16 < body >
17 < form id ="form1" runat ="server" >
18 < div >
19 < table >
20 < tr >
21 < td >< asp:Label id ="Label1" runat ="server" > Label </ asp:Label >< br >
22 < asp:TextBox id ="TextBox1" runat ="server" ></ asp:TextBox >< br >
23 < input type ="button" onclick ="btchange();" value ="Javascript Change" />
24 </ td >
25 < td >< asp:Label id ="Label2" runat ="server" > Label </ asp:Label >< br >
26 < asp:TextBox id ="TextBox2" runat ="server" ></ asp:TextBox >< br >
27 < asp:Button id ="Button1" runat ="server" Text ="Button" ></ asp:Button >
28 </ td >
29 </ tr >
30 </ table >
31 </ div >
32 </ form >
33 </ body >
34 </ html >
35
[Code-behind]1 Partial Class ButtonClickWithJavascript
2 Inherits System.Web.UI.Page
3
4 Protected Sub Page_Load(ByVal sender As Object,
5 ByVal e As System.EventArgs) Handles Me.Load
6 If Not Page.IsPostBack Then
7 Label1.Text = " Original Label "
8 TextBox1.Text = " Original Text! "
9 End If
10 End Sub
11
12 Protected Sub Button1_Click(ByVal sender As Object,
13 ByVal e As System.EventArgs) Handles Button1.Click
14 Label2.Text = " Button changed "
15 TextBox2.Text = Now().ToString()
16 End Sub
17 End Class
18
On first load, the page looks like this:
Clicking the "Javascript Change" button calls the client-side btchange() function which results in the text within the label and the first textbox being changed to give this:
Clicking "Button" causes a PostBack, and fires the server-side Button1_Click() event, which results in this:
The value of the first TextBox which was changed using Javascript was retained, but the value of the first Label, which was changed by the same Javascript routine, was not retained. So why is this? "Is there a bug in ViewState, which 'forgets' Label values?" asked the questioner. The answer is "No", but to explain this behaviour, a quick overview of ViewState is required, with links to more detailed explanations.
ViewState's job is to manage any changes to the initial state of server controls, if those changes are made programmatically on the server, or if changes made by user interaction are passed to the server. This does not include restoring the values of form inputs such as TextBoxes or the selected item in a CheckBox. There is a common misconception that form values are managed by ViewState. They are not. Never have been. These values are managed and restored purely by IPostBackDataHandler. IPostBackDataHandler is a massive boon to web developers who were brought up on other server-side technologies, such as classic ASP, PHP etc. In the "olden" days, we used to have to manually wire up every form field to display the originally posted value, so that user's weren't presented with an empty form to fill in all over again, if it had failed server-side validation. IPostBackDataHandler means we never have to do that again.
The part that Viewstate plays in the sample page above is easily examined using Fritz Onion's ViewStateDecoder.
On first load, ViewState only contains the Text value for Label1 - "Original Label ".
Again, ViewState is not responsible for form field values, so the Text value of the TextBoxes is not included. The reason why Label1 is incuded in ViewState, but Label2 is not is because Label2's Text value was set in the aspx - at Page Initialisation. Label1's Text value was initially set in the aspx markup as "Label". It was subsequently programmatically changed in Page_Load to "Original Label ". Remember, ViewState's job is to manage any changes to the initial state of server controls, if those changes are made programmatically on the server. Label1 falls into this category. You can test this by removing the Text value of Button1 from the aspx, and setting it in Page_Load to "Button". Now you see that Button1 is added to ViewState on first load also.
When the "Javascript Change" button is clicked, the client-side script alters the Text values of both Label1 and TextBox1. Looking at ViewState now will show no changes from the initial Page_Load. The page has not been posted back, and Javascript cannot alter ViewState, so this is no surprise. When "Button" is clicked, a PostBack is caused. Now looking at ViewState, we can see that Label2 has been included.
This is because it's initial value was programmatically changed in the Button_Click event. There were no changes in value for Label1, so its original value was restored from ViewState. The Text value of neither TextBox ever made it into ViewState at any stage. This property was managed purely by IPostBackDataHandler all the time.
How do we retain client-side changes to values or state of non-form field controls? Anything that happens on the client is totally shielded from the server, unless we let the server know. The server is completely unable to "read" the results of client-side operations, which is just as it should be for security reasons. So we need to let the server know that changes have taken place. One way to do this is to create a hidden field in the page. Hidden fields, being standard form fields will be looked after by IPostBackDataHandler, so any changes in value will be persisted across postbacks. It would be relatively simple to extend the client-side btchange() function to read and write changes to the hidden field, and copy changes to the Label.