Friday, January 18, 2008

Control.ClientID Extremely Useful for Creating Client-Side Functionality

The ClientID property is my favorite property this week and will definitely reshape how I write asynchronous functionality in the future.  I learned about this while adding some functionality to a client's site earlier this week.  The original site was created off-shore and people like me are the ones who get to take care of it.  It's been fun so far and had I not been working on this site this week I would've totally overlooked this wonderful property.

First off, ClientID is a readonly property (as it should be!) that does nothing more than get you the id of the control that is rendered.  Take the following rendered RequiredFieldValidator.

<span id="datalistContent_ctl05_requiredDescription" style="color:Red;visibility:hidden;">I saw what you did there.</span> 

Not quite the same as my original "requiredDescription" ID set in the .aspx.  When this control was processed this original ID was appended to the UniqueID created for this control. ASP.NET does this to make sure that there are unique id's for controls and this process is especially prevalent in Repeaters, DataLists, and GridViews.  Most of you probably knew that.  As you can tell from the HTML above my "requiredDescription" was in my DataList called "datalistContent" in the 5th generated table cell ("ctl05").

So how will this help me with asynchronous programming, cleaner UI's, etc, etc?  If you're familiar with the ItemDataBound event of the Repeater, DataList, Gridview controls then you can probably see why. Let's say I have a DataList that contains a TextBox and a HyperLink.  The Description property of my object is displayed in a multi-line textbox with a CSS style attribute (see below) that makes it appear to be normal text (i.e. can't see the border, scrollbars, etc..).  The link that reads "Change Description" should enable the already-disabled textbox, hide the link, apply a TextBox-like style, and focus the cursor on this newly-enabled TextBox.  After editing the text the onblur event triggers a client-side function that disables the textbox and adds the disabled, plain-ole-text feeling CSS, then display the link again for changing the description.  It's actually easier than it sounds (demo below). Take the following example (ps. If this were production code I would use e.Item.FindControl("string") as Foo and check for null before adding attributes):

protected void ItemDataBound(object sender, DataListItemEventArgs e)
   TextBox text = (TextBox) e.Item.FindControl("textDescription");
   HtmlAnchor link = (HtmlAnchor) e.Item.FindControl("linkChangeDescription");

   text.Attributes.Add("style", "border: none 0px #fff; overflow: hidden; width: 250px;");   
   text.Attributes.Add("onblur", string.Format("Blur('{0}', '{1}');", text.ClientID, link.ClientID));   
   link.Attributes.Add("onclick", string.Format("Edit('{0}', '{1}');", text.ClientID, link.ClientID));

Those JavaScript functions consist of a whopping 6 lines so I won't paste here.  If you are curious, leave a comment and I will send you a download link to AspNetAjaxLolSike.csproj. Quick tangent, if you are into Internet memes check out LOLCode.  Chances are you you have been on the receiving end of an image such as this or this.  Think of a programming language with that vernacular. Try-Catch = Hai-KThnxBye.  ANYWAY, that's where the LolSike project name came from as I spent a good 2-3 hours laughing uncontrollably at the user-submitted codebase at LOLCode.  Some guys at Microsoft actually created a compiler for it, which I have yet to track down.

Anyway, that code will render as follows with the appropriate client-side id of the ASP.NET Control from our code-behind:

<a href="#" id="datalistContent_ctl04_linkChangeDescription" onclick="Edit('datalistContent_ctl04_textDescription', 'datalistContent_ctl04_linkChangeDescription');">Change Description</a><br />
<textarea name="datalistContent$ctl04$textDescription" rows="2" cols="20" id="datalistContent_ctl04_textDescription" disabled="disabled" class="hidden" onblur="Blur('datalistContent_ctl04_textDescription', 'datalistContent_ctl04_linkChangeDescription');" style="border: none 0px #fff; overflow: hidden; width: 250px;">Hai 5</textarea><br /> 

Moving on, I came across exploring this functionality after getting tired of finding solutions requiring a Postback (i.e. asp:LinkButton control).  This, as far as I'm concerned: sucks.  The next best option was to wrap the datalist in an UpdatePanel.  That is wrong on so many levels.  My general attitude towards the UpdatePanel is that it generally sucks 80% - 90% of the time.  80% - 90% may seem like a large number and I will explain my feelings.  The UpdatePanel is a slippery slope of bad practices (imho) for those that want to start with asynchronous technologies.  I say this because most of those that use it (80% - 90%) don't know WHEN to use it.  I can't tell you how many times I've seen someone wrap a DataGrid of 100+ records (with update, add, delete functionality) in an UpdatePanel.  Set up a project in Visual Studio that has a textbox and button in an update panel.  On "postback" set the textbox to the current date time (DateTime.Now) and use Firebug or Fiddler to check out the size of the response.  It will probaby surprise you.  I'm not saying that if you use an UpdatePanel you suck at AJAX.  By no means do I consider myself the authority figure on asynchronous programming.

You can add a button to this (hidden if there are no records in your IEnumerable<T>) to give your application "Update" functionality.  Your button OnClick event would then check all the DataListItem's in the DataList, cast your DataListItem.Item.DataItem to type T, interpret the data, and process accordingly by way of some class library. Easy.

Notice that there are no postbacks when I edit the descriptions in my DataList.

ClientID Demo

When I get some more time I will explore the avenue of asynchronous GridView paging.  I have never used the GridView so I'll have to be REALLY motivated to try.  I only used this in the DataList because I needed to display columns based on user input and treated the DataList as a Repeater with Columns functionality.

No comments:

Post a Comment