hackification

Homepage

...rediscover the joy of coding

Closures: Simplify Event Handling Code

Closures are a really useful programming construct, but as they're only just appearing in the C-family languages, they're relatively new to me. In this article I show how they can be used to vastly simplify event-handler code in web development.

One problem I've had with this subject is the rather mathematical explanations given. Wikipedia says that "...a closure is a function that is evaluated in an environment containing one or more bound variables. When called, the function can access these variables". Unfortunately that doesn't exactly 'speak' to me, so I thought I'd present a code-based example.

The Problem

Let's say we have a web-page, containing a table, where the rows are identified by an identifier. (This is pretty common - displaying one row on the webpage for each row in a database table). On each row are delete links, and when clicked, they open an AJAX dialog in the webpage, confirming the deletion. When the user clicks 'OK' in the dialog, the row with that ID is deleted.

Pretty standard stuff hopefully. I'm going to omit loads of details to make this shorter - the dialog isn't even vaguely modal, and there's no cancel button, and the deleting won't work.

ASP.NET AJAX

So let's start with an ASP.NET AJAX solution. Here's my ASPX page (minus the boilerplate stuff like the UpdatePanel):
<asp:Repeater runat="server" ID="_repeater">
  <HeaderTemplate>
    <table>
      <tbody>
  </HeaderTemplate>
  <ItemTemplate>
        <tr>
          <td><asp:Label runat="server"
                Text='<% #Container.DataItem %>' /></td>
          <td><asp:LinkButton runat="server" Text="Delete"
                OnClick="_deleteLink_Click"
                CommandArgument='<% #Container.DataItem %>' /></td>
        </tr>
  </ItemTemplate>
  <FooterTemplate>
      </tbody>
    </table>
  </FooterTemplate>
</asp:Repeater>

<div runat="server" id="_dialog" visible="false"> <asp:Button runat="server" Text="OK" OnClick="_okButton_Click" /> </div>
And here's some of the code-behind:
protected void Page_Load( object sender, EventArgs e )
{
  _repeater.DataSource = new[] { 1, 2, 3, 4, 5 };
  _repeater.DataBind();
}

protected void _deleteLink_Click( object sender, EventArgs e ) { var linkButton = (LinkButton) sender; var id = int.Parse( linkButton.CommandArgument );

_dialog.Visible = true;

// Store 'id' somehow. }

protected void _okButton_Click( object sender, EventArgs e ) { _dialog.Visible = false;

// Retrieve 'id' somehow and delete the row. }
The problem occurs between the event handler for the delete link, and the event handler for the OK button. Getting the ID from the row is easy, but we need to get the value to the handler for the OK button.

There are solutions of course - we could store it in a hidden field of the dialog, or in the view state - but there's a big disconnect there. The scope of the OK button click handler is completely different to the scope of the delete link handler.

jQuery AJAX

I'm now starting to feel that ASP.NET AJAX is great for quick intranet applications, but for "real-world" internet applications, it's better to do more of the work client-side, 'by hand'.

The ASPX I'd use is very similar to the previous example - instead of ASP.NET click handlers, I'd add classes and ids to attach metadata to the table:
<asp:Repeater runat="server" ID="_repeater">
  <HeaderTemplate>
    <table>
      <tbody>
  </HeaderTemplate>
  <ItemTemplate>
        <tr>
          <td><asp:Label runat="server"
                Text='<% #Container.DataItem %>'
                CssClass="data-id" /></td>
          <td><a class="action-delete" href="#">Delete</a></td>
        </tr>
  </ItemTemplate>
  <FooterTemplate>
      </tbody>
    </table>
  </FooterTemplate>
</asp:Repeater>

<div id="delete-dialog" style="display: none;"> <input type="button" value="OK" id="delete-dialog-ok" /> </div>
(Of course you could generate the appropriate HTML with any HTML templating system).

Before I get to the interesting jQuery bit, I also need to define a handler - Delete.ashx - which will process the jQuery AJAX call:
public class Delete : IHttpHandler
{
  public void ProcessRequest( HttpContext context )
  {
    var id = int.Parse( context.Request["id"] );

// Delete the row here.

context.Response.ContentType = "application/json"; context.Response.Write( "{}" ); } }
That is admittedly more work than hooking an ASP.NET event handler. (You'd probably want to check the request was a POST somewhere in there too).

Now for the interesting jQuery bit:
$(function() {

// // When the user clicks a delete link... // $('.action-delete').click(function() {

// // Find the ID in the row, // var rowId = $(this).closest('tr').find('.data-id').text();

// // Show the delete dialog, //   $('#delete-dialog').show();

// // And attach a click handler to the OK button, // and when that is clicked... // $('#delete-dialog-ok').click(function() {

// // Initiate an AJAX POST, and when it succeeds... // $.post('Delete.ashx', { id: rowId }, function() { // // Unbind the OK button click, and hide the dialog. // $('#delete-dialog-ok').unbind('click'); $('#delete-dialog').hide(); }, 'json'); }); });

});
That's real magic. The 'rowId' local was created on the first 'delete link' click, but it remains available to the handler for the second OK button click.

The interesting thing here is that I haven't had to pass or manually store the ID anywhere. When the user clicks on an 'action-delete' link, I find the ID value, show the dialog, and then just attach a 'click' handler to the dialog OK button. Because the 'click' handler is inline, it has access to the variables in its parent's scope.

The other advantage to doing this this way, which has nothing to do with closures, is that the manual jQuery AJAX version doesn't talk to the server with every click, only on the final one. ASP.NET AJAX will chat to the server as the user clicks the delete links, giving a slightly slower feel.