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

January 2008 - Posts

Getting absolute coordinates from a DOM element

For some reason, there is no standard API to get the pixel coordinates of a DOM element relative to the upper-left corner of the document. APIs only exist to get coordinates relative to the offset parent. Problem is, it's very important to get those coordinates for applications such as drag and drop, or whenever you need to compare coordinates of elements that may be in completely different parts of the document.

In Microsoft Ajax, we implemented such a function (Sys.UI.DomElement.getLocation) but it proved to be one of the most difficult problems we had to solve. Not so surprisingly, every single browser has its own coordinate quirks that make it almost impossible to get the right results with just capability detection. This is one of the very rare cases where we reluctantly had to use browser sniffing and implement a completely different version of the function for each browser.

We also had to implement a pretty complex test suite to verify our algorithms for thousands of combinations of block or inline elements, offsets, scroll positions, frame containment, borders, etc., and run those on each browser that we support. The test suite in itself is quite interesting: it renders an element with the constraints to test, gets its coordinates from the API, creates a top-level semi-transparent element that is absolutely positioned and check that both overlap exactly to pixel precision. To do so, it takes a screen shot and analyses the image to find the rectangles and check their color.

The simplest runtime implementation is the IE one, thanks to a little-known API that does almost exactly what we want: getBoundingClientRect. Almost exactly as we quickly discovered it has a weird 2-pixel offset except on IE6 if the HTML element has a border (which is a "feature" that was removed in IE7 and which we chose not to support). It also doesn't include the document's scroll position. Finally, if the element is in a frame, the frame border should be subtracted. This actually was the cause for a bug that we unfortunately discovered after we shipped 1.0 but which is now fixed in ASP.NET 3.5 as the parent frame may not be in the same domain, in which case attempting to get its frame border will result in an access denied error. So you need to do this in a try-catch and just accept the bad offset in that fringe scenario.

In all other browsers, you currently need to recurse through the offset parents of the element and sum the offset coordinates.

In Safari 2, though, the body's offset gets counted twice for absolutely-positioned elements that are direct children of body. Something you don't just guess, you need the thousands of test cases I mentioned earlier to discover something like that...

In both Safari and Firefox, you must subtract to the coordinates the scroll positions of all parent nodes (and not the offset parents like before). Well, except if the element is absolutely positioned (this last restriction doesn't apply to Opera). Or if the parent is the body or html element. Confused yet? Wait, there's more.

In Firefox, non-absolutely positioned elements that are direct children of body get the body offset counted twice.

In both IE and Firefox (but you don't care for IE as it has getBoundingClientRect), the border of a table gets counted in both the border of the table and in the td's offset.

Finally, on Opera, there are scroll values on elements that are not scrolled so you need to explicitly check for the overflow mode before you subtract scroll positions. Opera also includes the scrolling into the offsets, except for positioned contents.

The worst part in all this is that we don't even know for sure that we nailed it, and we know that future browsers will require adjustments.

Speaking of which... Firefox 3 will implement getBoundingClientRect. I haven't tried their implementation yet and checked whether it has the IE quirks, but it should be a lot simpler than what we have to work around today to do the same thing and we'll definitely have to rely on less undocumented quirks. By the way, if you were thinking of using the undocumented getBoxObjectFor, forget it, it was designed for XUL elements and will probably get removed from non-XUL elements in future versions.

There is a bug open against WebKit to get that in Safari but it's currently without an owner. Here's to hoping this gets into the next version. Vote for it.

Opera apparently also implements that in 9.5, referencing a W3C draft which curiously doesn't contain any references to getBoundingClientRect.

A honest recap of the IE8 meta-tag controversy

This will affect all Web developers, which is precisely why the debate is very heated. Anyway, here's a honest recap of the issue. I tend to agree with the author's conclusion although that is obvisouly not the position of Microsoft...

http://arstechnica.com/articles/paedia/ie8-super-standards-mode.ars
http://arstechnica.com/articles/paedia/ie8-super-standards-mode.ars/2

Note: at the smallest signs of a flame war, I will close the comments on this post. Ars has a good forum and comment system where your voice is much more likely to be heard and endlessly contradicted. ;)

Improving PHP by running it in IIS

Mike Volodarsky wrote a fantastic article in the January 2008 issue of MSDN magazine in which he explains how you can take an existing PHP application (he uses QDig, a popular image gallery) and improve it without touching a line of its code. This is a great demonstration of the power of IIS 7's modular and pluggable architecture. Mike was able to add the following features to this PHP application using only managed code modules and configuration:

It really is amazing that you can add such complex features to an existing web application without touching its code. It is also super-simple to do. It looks very much like aspect-oriented programming if you think about it, only it's easy to set-up and understand.

The only thing I'd have liked to see and that isn't in the article is a comparison between the performance of the application on a LAMP setting with Zend's platform configured and the performance of the same application with Mike's improvements on IIS.

http://msdn.microsoft.com/msdnmag/issues/08/01/PHPandIIS7/default.aspx

Posted: Jan 25 2008, 12:13 PM by Bertrand Le Roy | with 4 comment(s) |
Filed under: , , ,
I'm now officially a slacker

Well, at least a Dot Net Slacker...

My first article on DotNetSlackers just got published. It shows how to handle the back button with ASP.NET Ajax using pure server code.

Check it out (registration required):
http://dotnetslackers.com/articles/ajax/HandlingTheBackButtonFromServerCode.aspx

Off-topic: I have the best daughter in the whole world

My wife was trying to explain Martin Luther King day to my four year old daughter today. It's pretty difficult to explain seeing all the cultural ramifications and references that we take for granted but that she doesn't have, so my wife starts by explaining how she (my daughter) might have noticed how people have different skin pigmentations. Guess what she answered to that...

"Oh, yes, I had a dream about that."

OMG, my daughter is a genius!

Dates and JSON

JSON is a great data format and it's taken the Internet by storm for a number of good reasons. But because of a strange oversight in the EcmaScript specs, there is no standard way of describing dates in JSON. There's been a lot of discussion on this topic and it still remains a problem today.

In the Microsoft Ajax Library, we've tried a number of different approaches before we converged to the solution we're using today.

The first thing we tried was to inject Date constructors in the JSON string. This is a (very) bad idea for a number of reasons. First, it simply does not conform to the JSON specs. Second, any JSON parser that validates its input before parsing it will cough on such a thing. Finally, it establishes a precedent: why would it be allowed for dates and not for arbitrary types? This would just defeat the purpose of JSON.

The second approach, which is what most people settled on, is to adopt some string form of date and just interpret any string that conforms to this format as a date. We've been using "@1163531522089@" where the number is the number of milliseconds since January 1st 1970 UTC for a while (not super-readable) and some people are just using the ISO 8601 format. These are both almost acceptable compromises (if everyone agrees on them) but have a fundamental flaw which is that there can be false positives: what if you want to serialize the "1997-07-16T19:20:30.45+01:00" string, as a real string, not as a date?

To be perfectly honest, JSON Schema does solve the problem by making it possible to "subtype" a string as a date literal, but this is still work in progress and it will take time before any significant adoption is reached.

Our current approach is using a small loophole in the JSON specs. In a JSON string literal, you may (or may not) escape some characters. Among those characters, weirdly enough, there is the slash character ('/'). This is weird because there actually is no reason that I can think of why you'd want to do that. We've used it to our benefit to disambiguate a string from a date literal.

The new format is "\/Date(1198908717056)\/" where the number is again the number of milliseconds since January 1st 1970 UTC. I would gladly agree that this is still not super readable, which could be solved by using ISO 8601 instead.

The point is that this disambiguates a date literal from a string that looks like the same date literal, while remaining pure JSON that will be parsed by any standard JSON parser. Of course, a parser that doesn't know about this convention will just see a string, but parsers that do will be able to parse those as dates without a risk for false positives (except if the originating serializer escaped slashes, but I don't know of one that does).

Here's how you'd encode a string that contains the same literal I described above: "/Date(1198908717056)/".

Notice how a simple eval-based parser will just return the exact same string in both cases, but if you run a simple search for "\\/Date\((-?\d+)\)\\/" and replace with "new Date($1)" before the eval (but after validation), you'll get the dates right in the final object graph. The string I described above will just go through that filter undetected and will remain a string.

We're pretty much satisfied with this solution to the date problem, but of course for the moment very few serializers and parsers support that convention. It would be great if this could become the consensus across the industry.

I'd love to read any comments you may have on that subject.

UPDATE: the regular expression can now find dates before 1970. Thanks to Rick and Marc for the tip.

Immutability in C#

For some reason, there's been a lot of buzz lately around immutability in C#. If you're interested in algorithms and data structures, it's a fascinating subject. Immutable objects, according to Patrick Smacchia, have the following advantages:

  • They simplify multithreaded programming.
  • They can be used as hashtable keys.
  • They simplify state comparison.

A good introduction to immutable types by Patrick Smacchia:
Immutable types: understand their benefits and use them

More on immutability usefulness:
Immutability, Purity, and Referential Transparency
Immutable types can copy the world… safely!

Luca Bolognese on implementing immutable value objects:
Creating an immutable value object in C# - Part I - Using a class
Creating an immutable value object in C# - Part II - Making the class better
Creating an immutable value object in C# - Part III - Using a struct
Creating an immutable value object in C# - Part IV - A class with a special value
Creating an immutable value object in C# - Part V - Using a library

Finally, Eric Lippert shows how to implement a few common data structures as immutable types:
Immutability in C# Part One: Kinds of Immutability
Immutability in C# Part Two: A Simple Immutable Stack
Immutability in C# Part Three: A Covariant Immutable Stack
Immutability in C# Part Four: An Immutable Queue
Immutability in C# Part Five: LOLZ! <- this is of course the must read of the series ;)
Immutability in C# Part Six: A Simple Binary Tree
Immutability in C# Part Seven: More on Binary Trees
Immutability in C# Part Eight: Even More On Binary Trees
Immutability in C# Part Nine: Academic? Plus my AVL tree implementation
Immutability in C# Part Ten: A double-ended queue
Immutability in C# Part Eleven: A working double-ended queue

Posted: Jan 16 2008, 03:36 PM by Bertrand Le Roy | with 6 comment(s) |
Filed under: ,
Screencast: how to enable server-side history management in an ASP.NET Ajax application

I've recently recorded a screencast showing how to enable server-side history management (in other words, handling the back button) in an ASP.NET Ajax application. The whole video is less than 15 minutes total and I build the whole application from scratch in there (in VB).

I hope it shows just how simple history management is made by ASP.NET Ajax and that it helps understanding the state model that it's built on.

The same video in a variety of formats and in a resolution where the code is actually readable can be found here:
http://www.asp.net/downloads/3.5-extensions/

For those people who find it difficult to cut and paste code from a video by doing a screen capture and then using OCR over it, here's the source code of the mini-app that's being built in the video:

Default.aspx:

<asp:ScriptManager ID="ScriptManager1" runat="server" 
    EnableHistory="True" EnableStateHash="False"/>
<
asp:UpdatePanel ID="UpdatePanel1" runat="server"> <ContentTemplate> <asp:Wizard ID="Wizard1" runat="server"> <WizardSteps> <asp:WizardStep runat="server" Title="Step 1"> <asp:TextBox ID="TextBox1" runat="server"/> </asp:WizardStep> <asp:WizardStep runat="server" Title="Step 2"> <asp:TextBox ID="TextBox2" runat="server"/> </asp:WizardStep> <asp:WizardStep runat="server" Title="Step 3"> <asp:TextBox ID="TextBox3" runat="server"/> </asp:WizardStep> </WizardSteps> </asp:Wizard> </ContentTemplate> </asp:UpdatePanel>

Default.aspx.vb:

Partial Class _Default
    Inherits System.Web.UI.Page

    Protected Sub Wizard1_ActiveStepChanged(_
ByVal sender As Object, ByVal e As System.EventArgs) _
Handles Wizard1.ActiveStepChanged
If ScriptManager1.IsInAsyncPostBack And _
Not ScriptManager1.IsNavigating Then
ScriptManager1.AddHistoryPoint("index", _ Wizard1.ActiveStepIndex, _ "Wizard step " & Wizard1.ActiveStepIndex) End If End Sub Protected Sub ScriptManager1_Navigate(_
ByVal sender As Object, ByVal e As HistoryEventArgs) _
Handles ScriptManager1.Navigate
Dim indexString As String = e.State("index") If String.IsNullOrEmpty(indexString) Then Wizard1.ActiveStepIndex = 0 Else Dim index As Integer = Convert.ToInt32(indexString) Wizard1.ActiveStepIndex = index End If Page.Title = "Wizard step " & indexString End Sub End Class

http://www.asp.net/downloads/3.5-extensions/

More Posts