Tuesday, August 12, 2008

ASP.Net Ajax PopupControlExtender gotchas

I recently completed a user control that included a PopupControlExtender which was used in repeater on the user control's host page. I'm using the version of ajax that ships with visual studio 2008 (pre sp1). I came across several gotchas:

  • Technically, the first gotcha is that my user control cannot include a ScriptManager since there can only be one per page, and since the user control is repeated many could be created. Thus, the host page must own the ScriptManager effectively rendering my user control with my PopupControlExtender non-self containing. I think this is a clumsy limitation of the current state of ajax.
  • The second gotcha applies regardless of the fact that there are more than one instance of the PopupControlExtender on the page: I don't want the object that pops up to be triggered when the referenced TargetControlID control receives focus (I can hardly imagine a scenario when someone would want this), I want it to pop up on a button click. Thus, I must pop it up manually. But when I do so, I must know (or learn by trial and error) that the client framework captures page events and hides my popup (so when you click on other controls outside your popup it closes). My pop function must look like the following. Without "event.cancelBubble" my popup won't pop up.
function pop(popExtenderID)
event.cancelBubble = true;
var popExtender = $find(popExtenderID);
// this one is the next gotcha
popExtender.set_OffsetY(popExtender.get_OffsetY() + document.body.scrollTop);
  • This next one is also irrespective of the fact that my control is in a repeater. The PopupControlExtender doesn't seem to account for scroll position. Thus the call to set_OffsetY in the snippet above. Even so, sometimes the position of the popup will be at the very top of the page. I haven't been able to figure out why it doesn't always work; it doesn't seem to be consistent.
  • This one is certainly repeater related. The function above takes as a parameter the ID of a PopupControlExtender. If there are more than one, we have to accept the right one (the function is part of my user control so it will be repeated causing only the last one written to be the function that is used for all of my popups). So in the code behind of my user control, I had to add the following in order to pass in the "current" extender. Note that I had to ensure that the BehaviourID of the extender was unique. The replace call is to make the ID conformable to HTML rules.
var ExtenderBehaviourID = '_' + Guid.NewGuid().ToString().Replace( '-', '_' );
this.PopupControlExtender1.BehaviorID = ExtenderBehaviourID;
this.btnPop.Attributes.Add( "onclick", "pop('" + ExtenderBehaviourID + "');" );

  • This one was caused by the fact that I wrapped the controls within my pop up in an UpdatePanel because they made server postbacks. The code in the snippet above should only run when the host page loads or posts back, but not when my user control posts back. What I had is a search edit box/button and a GridView within my popup. The search button causes a postback and so does paging on the grid. Within these postbacks, I don't want my extender to receive a new unique id because if it does the javascript back on the page will receive the wrong ID - why I can't quite remember - I have a function that closes the popup. In this case "this.IsPostback" doesn't work (nor does this.Parent.IsPostback which maps to the same result) because this would represent the host page's postback. My hack elegant solution to this problem was to examine the BehaviourID of the PopupControlExtender. By default, that BehaviourID would contain the ID of the PopupControlExtender itself. If it is a postback of my control, I would've already changed that BehaviourID to be something like a guid (from the snippet above). So here's the condition:
private bool IsPostBackThisControl()
return !this.PopupControlExtender1.BehaviorID.Contains(PopupControlExtender1.ID);