Tales from the Evil Empire

Bertrand Le Roy's blog

News


Bertrand Le Roy

BoudinFatal's Gamercard

Tales from the Evil Empire - Blogged

Blogs I read

My other stuff

Archives

August 2005 - Posts

Why don't callbacks send the latest values of the form fields?
I've been getting this question a lot lately. Yes, it will stay that way and yes, it will confuse people.
The reason for that is very complex but I'll try to explain briefly.
First, you need to know that it's impossible to update the viewstate during a callback because contrary to postbacks, there can be multiple simultaneous callbacks which could cause an inconsistency or concurrency issue in the state.
Now, if we sent the latest version of the form, because of that, change events would fire on every single callback, each of which could have side-effects.
But there's worse. Some complex callback controls, like TreeView, need to manage their own client-side state (like the list of nodes that have been expanded client-side or using populate on demand). This client-side, control-specific state is kept in a hidden field that's used on the next postback to replay the changes and update the viewstate. Rehydrating the state from ViewState is cheap, replaying changes is not because that may involve multiple calls to a database.
Again, if we sent the latest version of the form during a callback and if we want the state of the page to be consistent, the tree would replay all the changes that have been done client-side since the last postback, on every callback. That would result in an unacceptable performance drop.
And you'll have the exact same problem with any complex data control that needs to maintain client-side state about the changes that occurred since the last postback. The ViewState can't be used for this state because it can't be updated during callbacks, so to maintain a consistent state, you need to replay the changes during the next postback, but not during each callback because the changes could add up pretty fast, growing the total number of operations to replay as an o(n^2) function of the number of callbacks.
There are more intricate details involved but this is the big picture.
The good news is that the advice here is simple: you need to package any form fields you need to use to determine your callback response into your callback parameter. To help you package multiple, strongly-typed pieces of information into the single string callback parameter, check out my RefreshPanel library.

More details about that here.
How to output HTML at the end of a WebForm from code?

With all this client-side activity that's going on lately, there's one particular need I've seen arise repeatedly. It is often useful to output contents at the end of a page (or at the end of the form, at least). The reasons why you would want to render contents not in the current position in the stream of the rendering page but at the end are diverse. One reason could be that you want only one instance of that contents on the page no matter how many controls require it. Another could be that you want controls to be able to add to a big chunk of data that you want to output in one piece at the end (that's a need we had in Atlas).

Well, if you happen to have that particular need, there's one API that will help you immensely despite its name which only reflects its original intention:

Page.ClientScript.RegisterStartupScript(typeof(YourControlOrPageType),
   
"KeyUniqueToThisBlock",
   
"<div>This will be rendered before &lt;/form&gt;.</div>"
);

This call will put your HTML chunk aside while it's rendering the end of the form and will ouput it just before the </form> tag. The key parameter ensures that only one instance of this HTML fragment will be rendered no matter how many times the call was made. You can make this call from anywhere, although it's usually done from PreRender. Render is OK for this client script registration if you're in a control that can only render in a form (it's too late for other client script register APIs, and it's only OK from page if you call the base method after it.). There must be a <form runat="server"> on the page for this to work.

Finally, I think I should mention that the order in which blocks are registered is the order in which the blocks will be output.

(thanks to Chris Sharpe for this post idea and the previous one)

UPDATE: Peter Blum just pointed me to another use of this idea. If you need to output some kind of popup as part of a control's rendering, the popup really doesn't belong next or inside the rendering of the main part of the control. Furthermore, it may be rendered as a DIV, which is invalid inside a SPAN or other inline elements, so it has to be put someplace neutral like the end of the form to ensure XHTML compliance. This little trick is perfect for this use.

How to put a DIV over a SELECT in IE6?

Everybody who tried to implement an HTML menu knows it: in Internet Explorer 6, there are some elements like SELECT that always appear in front of all other elements no matter what z-index you apply to them. Other elements that present the same problem are ActiveX controls (including Flash movies), OBJECT tags, plug-ins and iFrames. That's because these elements are implemented as windows whereas other elements are just drawn on the existing background window.

<rant>This sucks. Really. It's a typical case where an implementation detail forces web developers to go through hoops. This problem is IE only and everything works fine in all other modern browsers.</rant>

Now, is it possible to work around it? There is always the possibility of a getElementByTagName and of hiding all such elements from the page when showing an overlaid DIV. Uck. That would really puzzle your users even though it doesn't really impair usability very much in the case of a menu which will go away as soon as you take the mouse cursor out.

There is actually a much better workaround, and that's what the new ASP.NET menu is using. I suspect that most other professional menu controls do the same. Let me warn you that it's very hacky and takes advantage of a behaviour that seems as buggy as what we're trying to fix, but it's also very efficient and seamless.

The idea is to put an iFrame (which is also a windowed control) on the page at exactly the same location as your DIV. The iFrame must be added at the end of the page so that it appears in front of all other windowed controls (the windowed controls are stacked in the order in which they appear on the page). That takes care of covering any SELECT that may be in our way. Now, you may wonder what good that will achieve as I'll just cover my DIV with an iFrame instead of with a SELECT: that shouldn't buy me anything. Well, for some strange reason, it does. IE seems to be utterly confused by our little trick and just shows the DIV in front of the iframe if and only if you position the DIV after you've positioned the iFrame. It seems to forget about the windowed nature of the iFrame for what it renders after it.

If you do it using javascript, just set the position of your DIV after you've set-up the iFrame. If you do it in the HTML markup, just define the DIV after the iFrame. Here's HTML markup that successfully displays a DIV in front of a SELECT element:

<select>
 
<option>This usually appears on top in IE</option>
</select>
<iframe src="javascript:'&lt;html&gt;&lt;/html&gt;';" scrolling="no" frameborder="0"
 
style="position:absolute;width:50px;height:120px;top:0px;left:0px;border:none;display:block;z-index:0"></iframe>
<div style="position:absolute;width:50px;height:120px;top:0px;left:0px;border:solid 1px black;z-index:0">
 
This appears in front of the select in IE
</div
>

<select>
 
<option>This usually appears on top in IE</option>
</select>
<iframe src="javascript:'&lt;html&gt;&lt;/html&gt;';" scrolling="no" frameborder="0"
 
style="position:absolute;width:50px;height:120px;top:0px;left:0px;border:none;display:block;z-index:0"></iframe>
<div style="position:absolute;width:50px;height:120px;top:0px;left:0px;border:solid 1px black;z-index:0">
 
This appears in front of the select in IE
</div
>

One thing is worth noting: if you work with a secure site (with an https: protocol), you can't use "about:blank" as the url of the iFrame, otherwise you'll get a warning from IE saying that the page uses both secure and insecure contents. Not very nice to your users. So in this case, you'll just need to point the iFrame to some blank but secure page on your site. That's an extra hit to the server but hey, you already have a hit for each image on your site and that doesn't prevent anyone from having dozens of images on each page. Only in this case, it won't get cached client-side. See update below for a workaround...

Update: using "javascript:;" as the src of the iFrame in the https: case does the trick without the additional hit to the server. Thanks, Scott.

Update to the update: "javscript:;" is fine if your div is not transparent, but it will display an error message on the iFrame, so it should be avoided for transparent divs. By the way, if your div is transparent, you need to make the iFrame itself completely transparent (using an Alpha filter). That won't affect the hiding power of the iFrame (selects will actually not show through the div).

Update to the update to the update: thanks to David Anson and Kirti Deshpande who pointed me to this neat trick: using "j avascript:'<html></html>';" works well in all cases, even transparency, and avoids the https alert.

LAST UPDATE to this post: Folks, it might be a good time to stop and consider how much it costs you to support IE6. This hack to hide selects is only necessary for older versions of IE that are shrinking fast (6 or smaller; 7 and 8 are NOT affected). I actually think that it is now hurting more than helping to make your site look better in IE6. Give them a reason to upgrade, and show them how. That may be a better use of your time... Just an opinion, but please consider it.

How to deal with the July CTP ICallbackEventHandler breaking change?

As some have already noticed, there's been a change in the ICallbackEventHandler interface signature. The old interface had only one method, RaiseCallbackEvent, that took a single string parameter, and returned a single string response.

But this didn't work well with asynchronous data sources, where you need to initiate the request first and then act upon the results when they become available. There are no out-of-the box asynchronous data source controls in ASP.NET 2.0, but the data source interfaces are asynchronous-looking even if the implementation is synchronous. Third parties will no doubt construct asynchronous data sources. So we had to split the callback interface in order to enable it to play nice with asynchronous data.

First, you need to get the data by implementing the new RaiseCallbackEvent, which takes the same string parameter as the old RaiseCallbackEvent but does not return anything. This is typically where you'd start an asynchronous operation. If you're not, just store this data in a private variable for the moment.

Second, you need to use the data and render out the results of the callback by implementing the new GetCallbackResult, which takes no argument and returns the same string as the old RaiseCallbackEvent.

So if you already have code that uses the old interface, here's a very simple way to become compatible with the new interface while keeping your code relatively unchanged:

private string _callbackArg;

void
ICallbackEventHandler.RaiseCallbackEvent(string eventArgument) {
  _callbackArg = eventArgument;
}

string
ICallbackEventHandler.GetCallbackResult() {
  return
RaiseCallbackEvent(_callbackArg);
}

protected virtual string RaiseCallbackEvent(string
eventArgument) {
  // Your old implementation lives here unchanged
}

In a nutshell, take your old implementation and just change it from string ICallbackEventHandler.RaiseCallbackEvent(string arg) to protected virtual string RaiseCallbackEvent(string arg) and paste the code above.

More Posts