Position.cumulativeOffset() in Safari
I discovered a workaround today for a bug that was plaguing me: trying to use a script.aculou.us Sortable list in a fixed position box. Works fine on Firefox and IE, but it just wouldn’t behave in Safari — not even the newest version. After playing with it for a while today I realized why it was broken: the position of the element being dragged that was understood by the drag-and-drop code was never consistent with where the element actually was.
It turns out that Safari (and by extension WebKit) reports the wrong value for the offsetParent attribute — elements with position: fixed shouldn’t have an offset parent, because such elements are fixed to the viewport, and are thus independent of any parent nodes in the HTML tree.
The prototype.js framework has a method called Position.cumulativeOffset(), which scriptaculous’s drag-and-drop support depends on. Because of the bug in WebKit, the cumulative offset will be calculated wrong any time you have a fixed-position element inside a relative- or absolute-positioned element. That may not be such a common case, but it’s certainly one we use a lot in Dabble DB.
Fortunately, fixing this problem proved to be relative simple. Prototype already has a workaround built-in for WebKit, so I just added a few lines of code to tell it to stop calculating the offset when it reaches a fixed-position offsetParent element, and instead add the current scroll position of the document.
Here’s the code. The lines I added are 4 through 9 one (line 2406 in v1.6.0 of prototype.js or line 3265 in v1.5.1.1).
Element.Methods.cumulativeOffset = function(element) {
var valueT = 0, valueL = 0;
do {
if (Element.getStyle(element, 'position') == 'fixed') {
// Safari gets fixed position elements' offset parents wrong
valueT += document.body.scrollTop;
valueL += document.body.scrollLeft;
break;
}
valueT += element.offsetTop || 0;
valueL += element.offsetLeft || 0;
if (element.offsetParent == document.body)
if (Element.getStyle(element, 'position') == 'absolute') break;
element = element.offsetParent;
} while (element);
return Element._returnOffset(valueL, valueT);
};
Update: the initial code I posted wasn’t quite correct. I’ve updated the code.