Monday, April 13, 2009

Controls Within UpdatePanel Causing Full Postback

So today I was wrapping up a little project I've been working on. As one of my final steps I was going through the app and wrapping ASP UpdatePanels around the parts of my pages that I didn't want to perform full postbacks. This was going just swimmingly until I hit one page where the UpdatePanel just didn't seem to want to work. It didn't cause any errors or anything but when I clicked on controls within the panel the page did a full postback every time. Did some searching on the web and found that one likely culprit for this symptom is a dyncamically created LinkButton with no ID. Such a beast would likely be found within a data bound control for instance. But in this case, although I certainly was using a LinkButton it clearly had an ID assigned to it already. So it didn't seem like that was my problem. Finally through Google I found an old page at MSDN blogs that looked like it might contain information of worth but MSDN blogs is a shit site and was down for a change. Luckily, the GOOG's cache saved the day. The cached version of the page can be found here (at least as I type this it can): The cached article What I found within that article is that for some reason if an UpdatePanel is the direct child of a table row element then it just doesn't work. And sure enough my code looked like this:
<asp:UpdatePanel ID="TagsUPNL" runat="server">
 <ContentTemplate>
  <tr>

   <td style="vertical-align: top;" class="LeftAlign">
      <asp:LinkButton ID="myLB" runat="server" Text="some text" OnClick="MyLB_Click" />

   </td>
   <td style="vertical-align: top;">
    Control to update ... 
   </td>
  </tr>

 </ContentTemplate>
</asp:UpdatePanel>







What I intended with the above was that the entire contents of the table row (both tds in other words) would be included in the partial post-back. But due to this odd detail of the UpdatePanel's implementation this is not a valid way to achieve this. The fix was to use two UpdatePanels and a trigger, like so:
<tr>
 <td style="vertical-align: top;" class="LeftAlign">
  <asp:UpdatePanel ID="TagsUPNL" runat="server">

   <ContentTemplate>
    <asp:LinkButton ID="AddTagLBTN" runat="server" Text="Add Tag" OnClick="AddTagLBTN_Click" />

   </ContentTemplate>
  </asp:UpdatePanel>
 </td>
 <td style="vertical-align: top;">

  <asp:UpdatePanel ID="TagsUPNL2" runat="server">
   <Triggers>
    <asp:AsyncPostBackTrigger ControlID="AddTagLBTN" EventName="Click" />

   </Triggers>
   <ContentTemplate>
    
   </ContentTemplate>
  </asp:UpdatePanel>
 </td>

</tr>




And that worked!

Wednesday, April 8, 2009

Declaratively Binding ASP.NET Server Control Properties

Today I had the need to bind the 'Visible' property of an ASP.NET PlaceHolder control to a boolean property defined in my code-behind. This was certainly not the first time I've had to do this but it seems like every time I need to do it I have a hard time remembering the correct way to do it. So now that I've figured it out (again) I'm going to make a short post here about how to do it, both for my reference and yours. With client-side controls you can use the <%= %> operators to bind properties to code-behind generated values. For instance, let's say that for some reason I wanted to define the URL to my home page in my code-behind. I could put a property for accessing this value in the code-behind like so:
protected string HomePageUrl
{
 get
 {
  return "http://mysite.com/default.aspx";
 }
}

Then in my .aspx file I could create my client-side href tag declaratively like so:
<a href='<%= HomePageUrl %>'>Home</a>
That works just fine. But with server-side controls the above method will simply not work. In my case I wanted to bind the Visible property of the PlaceHolder control to a boolean property in my code-behind. Let's assume a simple property that always returns true:
protected bool ShouldBeVisible
{
 get
 {
  return true;
 }
}

And then we try to use the same technique we used with the anchor tag with the PlaceHolder:
<asp:PlaceHolder ID="PH1" runat="server" 
Visible="<%= ShouldBeVisible %>" />


This builds OK but when we try to load the page we get a Parser Error: "Cannot create an object of type 'System.Boolean' from its string representation '<% ShouldBeVisible %>' for the 'Visible' property." This is causing a problem for two reasons as far as I understand it. Firstly the <%= %> operator returns a string, not a boolean. Secondly the <%= %> operator doesn't get executed until the page is being rendered. This is way too late in the page lifecycle for the property to properly evaluated and applied. Luckily there is a simple way to solve the problem and that is to use the binding operator instead: <%# %>. This operator gets processed during calls to the DataBind method of whatever it's parent is. In my case I was doing this inside a ListView and the call to the ListView's DataBind method was enough to cause the operator to be evaluated. You can however do this anywhere on your page. Just remember that to get the operator to actually be processed you'll need to call Page.DataBind() somewhere. The Page_Load event handler is usually going to be the best place for this probably. So the proper code to bind the Visible property of my PlaceHolder to a value from the code-behind is:
<asp:PlaceHolder ID="PH1" runat="server" 
Visible="<%# ShouldBeVisible %>" />


Hope this helps you out!

Friday, April 3, 2009

Centering Non-Text Content (Like Buttons) In A Div

Recently I was working on a web form and had the need to center a button below a text box on the form. I was using a table-less layout and I have to admit at first I was stumped. Basically what I had was a div that contained the text box and the button. To center the button I put another div around it and tried setting text-align: center on that div. Of course that didn't work since a button is not text. So I did some searching and learned that the correct way to do this is to set the width on the div containing the element to be centered and then to set margin: 0px auto;. The real key to making this work however (and since I didn't see anyone point this out on the sites that helped me figure this out I'm writing this post now) is to set the width of the div containing the button to be just big enough to contain the button. I originally tried setting the width of the button container div to be the width of the parent container div and my button wasn't centered at all. It was only when I set the width of my button container div to be roughly the width of the button that I started to see it moving towards the center. And when you think about this it makes sense; setting the left and right margins to auto allows the browser to center the div when the page is rendered. But if the div is wider than the element being centered then it is the div being centered rather than the element within it. By matching up the sides of the containing div with the edges of the button we force the centering of the div to take the button with it. Sample code:
<html>
 <head>
  <style type="text/css">
   #PageContainer
   {
    width: 800px;
    background-color: Wheat;
   }
   #CenteredButtonContainer
   {
    width: 85px;
    margin: 0px auto;
   }
  </style>

 </head>
 <body>
  <div id="PageContainer">
   <p>
    This is an example paragraph.  Isn't it great?
   </p>
   <div id="CenteredButtonContainer">
    <input type="submit" />
   </div>
  </div>
 </body>
</html>