65.9K
CodeProject is changing. Read more.
Home

Persist JavaScript changes on postback

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.93/5 (9 votes)

Jun 5, 2013

CPOL

3 min read

viewsIcon

30434

Use JavaScript to let your web user make changes, and then retrieve those changes server-side on postback.

The Problem

I have been developing a simple XML-based message board (I'll write an article on that when the project is done.) I wanted to let forum users select an image to serve as their avatar on the boards, but having to reload the page every time the user selected an image was a pain: the reload time was noticeable, and each postback created a history entry that was annoying. The obvious answer was to use JavaScript to update the user's image; that worked fine. Unfortunately, changing the src attribute of the generated img did not persist when I did want a postback to save the information.

The Solution

First off, here is my original HTML:

<table>
  <tr>
    <td rowspan='2'><asp:Image ID="UserAvatar" runat="server" /></td>
    <th><asp:Literal ID="UserName" runat="server" /></th>
  </tr>
  <tr>
    <td>Click on an image below to change your avatar.</td>
  </tr>
  <tr>
    <td></td>
    <td><asp:Button ID="SaveButton" runat="server" Text="Save" OnClick="SaveButton_Click" /></td>
  </tr>
  <tr>
    <td colspan='2'><asp:Literal ID="ConfirmationText" runat="server" Visible="false" 
      Text="Your choice has been saved." /></td>
  </tr>
</table>

Pretty straightforward. The second row has only a single cell, as the cell containing UserAvatar stretches down to fill the first cell position thanks to the rowspan attribute.

The solution I found starts with adding a hidden input field somewhere on the form; I put this underneath the code for my table:

<input type="hidden" id="persistAvatar" value="*" runat="server" />

Note the use of the runat attribute. This is important. Just as an ASP control gets rendered as vanilla HTML, you can effectively turn vanilla HTML into an ASP control by using this attribute: it instructs the compiler to allocate space in the view state and allow it to be accessed in code-behind. I think this is functionally equivalent to using a asp:HiddenField control, but I haven't tested it. The default value is so that I can test if the field has been used or not.

The next step was to add the JavaScript that would update the UserAvatar image:

function setAvatar(e) {
  var img = document.getElementById('ctl00_ContentHolder_UserAvatar');
  var hold = document.getElementById('ctl00_ContentHolder_persistAvatar')
  img.src = e.getAttribute('src'); ;
  hold.value = e.getAttribute('src');
  return true;
}

Note that, because of the runat, the id for persistAvatar is mangled with the master page, exactly as if it were an ASP control. e is the image that the user clicked. The src of UserAvatar  and the value of persistAvatar are set to the source of the clicked image. The available images are arranged in a table on the page; a typical image looks like this:

<img src='myserver/images/avatar/angry_baby.jpg' alt='' onclick='setAvatar(this);' />

So now, all of the pieces are in place: the user clicks an image, the selected image appears in the form, and the name of the file is saved to a hidden field, all without a postback.

One other piece of infrastructure needs to be added, to make sure that the avatar image is loaded properly out of the view state. In the page's Load event, I have this code:

If IsPostBack AndAlso persistAvatar.Value <> "*" Then            
    UserAvatar.ImageUrl = persistAvatar.Value
Else
    UserAvatar.ImageUrl = String.Format("/images/avatar/{0}", MyUser.ForumAvatar)
End If

When the page is first loaded, or on postback if the user made no change to the avatar image, UserAvatar is set to show image that already exists in the data store. If the user has clicked something else, then UserAvatar is set to the src of the selected image, which had been saved in persistAvatar. When the page is delivered to the user, the displayed image will be correct.

And so we come to the last bit, when the user actually clicks on the Save button and a postback occurs. Simplicity in itself:

Protected Sub SaveButton_Click(ByVal sender As Object, ByVal e As System.EventArgs)
    Dim U As User = Access.GetUser(MyUser.UserId)
    U.ForumAvatar = System.IO.Path.GetFileName(UserAvatar.ImageUrl)
    ConfirmationText.Visible = Access.UpdateUser(U)
End Sub

I retrieve the currently stored data for the web user, parse out the name of the designated image file, save it to the data structure, and update the data store.

Moving On

It was a bit of a challenge finding out how to do this, as it's not exactly intuitive. But once I found some examples (and many thanks to Tadit Dash for pointing me in the right direction) I realized that it is pretty simple. This technique should be easily extensible, with a hidden field for each piece of data that you need to persist.

If you have anything to add, let me know in the comments. And as always, if you found this useful, please vote up.