Monday, April 28, 2008

Javascript

Pulled into battling the multi-standardised demons of Javascript for my latest client, I find myself referring to the usual haunts for solutions to problems that shouldn't exist. Javascript started as a simple language for supporting lightweight interactivity on a web page - clicks and prompts and updating the document in response. Unfortunately it was caught in the browser wars and since has become the cornerstone of application grade user interfaces never dreampt of when it was conceived.

The suprising thing is that almost without exception, I haven't found a single Javascript resource that always gets the right answers. Take the simple problem of locating the mouse. Search on Google for mouse position javascript and the first page of links all take you to solutions that will fail under one circumstance or another.

Bad examples include:


  • Retrieving mouse-x and mouse-y in two separate functions, both of which have to traverse the same document model.
  • Relying on browser detect functions to decide how to access properties
  • Ignoring browser types completely and only functioning in IE
  • Only solving the first half of the problem - finding the mouse on the page. If you don't know where your document element is, you have no context for the rest of your code.
  • Failing to handle the case where the document has been scrolled
  • Failing to handle the case where your event occurs within an IFRAME


Of all of these, Quirksmode only falls into the last trap. If you're handling an event from an IFRAME, you should be careful to account for the scroll offset of the frame itself and not the document that the event handler resides in. This function does exactly that:


function mousePos(evt, relativeTo) {
var xPos, yPos;
if (evt.pageX || evt.pageY) {
// Netscape
xPos = evt.pageX;
yPos = evt.pageY;
}
else if (evt.clientX || evt.clientY) {
// Probably IE
var docn = evt.srcElement ? evt.srcElement.ownerDocument : evt.target.ownerDocument;

xPos = evt.clientX + docn.body.scrollLeft + docn.documentElement.scrollLeft;
yPos = evt.clientY + docn.body.scrollTop + docn.documentElement.scrollTop;
}

var locn = {x: xPos, y: yPos};
if( relativeTo ) subtract( locn, relativeTo );
return locn;
}

function sutract( point1, point2 ) {
point1.x = point1.x - point2.x;
point1.y = point1.y - point2.y;
}


Note that the function takes the event object and an optional parameter for the position of the element that you want to find the mouse location relative to.

Now, all you need to know is where that element is relative to the document it resides in, and you can get a fix on your mouse event. Here's the code, which takes account of elements that can be scrolled.


function findPosition( obj ) {
var xPos = yPos = 0;

var start = obj;
while (obj && obj.offsetParent) {
xPos += obj.offsetLeft;
yPos += obj.offsetTop;
// Don't include scroll offsets for myself, or my parent document
if( obj != start && obj != obj.ownerDocument.body && obj != obj.ownerDocument.documentElement ) {
xPos -= obj.scrollLeft;
yPos -= obj.scrollTop;
}
obj = obj.offsetParent;
}

return {x: xPos, y: yPos};
}

0 comments: