Friday, December 14, 2007

Anonymous Function Fun

Yahoo!'s Julien Lecomte wrote an interesting piece regarding the perils of using innerHTML.

One of the "bad things" that can happen are memory leaks caused by circular references. He provides this inner function which breaks the reference --
     (function (o) {

var a = o.attributes, i, l, n, c;
if (a) {
l
= a.length;
for (i = 0; i < l; i += 1) {
n
= a[i].name;
if (typeof o[n] === 'function') {
o
[n] = null;
}
}
}

a
= o.childNodes;

if (a) {
l
= a.length;
for (i = 0; i < l; i += 1) {
c
= o.childNodes[i];

// Purge child nodes.
arguments
.callee(c);

// Removes all listeners attached to the element via YUI's addListener.
YAHOO
.util.Event.purgeElement(c);
}
}

})(el
);

If you're new to JavaScript and if the syntax isn't familiar, it's
worth a closer look.

First, the function has no name. It's an anonymous function
that accepts one argument, the object 'o'.

Because it's an anonymous function and since there's no
reference to the function whatsoever, you can't use "this" to
refer to the function, but you can always do this --

arguments.callee(c);

All functions implicitly have the property "arguments" which
behaves somewhat like an array but isn't ( you can iterate
arguments like an array and get its length, but that's where the
similarities end). More about the arguments property is found
here.

Arguments themselves have properties and one of those is
"callee."

Think of the callee as the currently executing function which
substitutes for the "this" object.

Notice that once the function is defined, it's immediately
executed. We see this at the very end --

(function (o) {
...
})(el);

If all of this is still puzzling, we can rewrite Julien's function to
look like --

var breakCircularReference = function (o) {

var a = o.attributes, i, l, n, c;
if (a) {
l
= a.length;
for (i = 0; i < l; i += 1) {
n
= a[i].name;
if (typeof o[n] === 'function') {
o
[n] = null;
}
}
}

a
= o.childNodes;

if (a) {
l
= a.length;
for (i = 0; i < l; i += 1) {
c
= o.childNodes[i];

// Purge child nodes.
this
(c);

// Removes all listeners attached to the element via YUI's addListener.
YAHOO
.util.Event.purgeElement(c);
}
}
}

breakCircularReference(el);


We've given the once anonymous function a name -
breakCircularReference.
This allows us to reference it which means that "this"
refers to the function breakCircularReference. We no longer
have to use arguments.callee.

The name also allows us to call it --

breakCircularReference(el);

which I think is easier to read and understand for new
JavaScripters.

Have fun and happy holidays!

Monday, December 03, 2007

Bindows Gauges

Recently, Bindows released a set of very functional gauges. These are "provided free of charge." They're built on top of their excellent Bindows component library.

The gauges are built from their BiGauge2 component.

One of the great things about these gauges is that it not only gives you a flavor of what Bindows can do, but that at least with these gauges, you don't need Bindows to manipulate them.

All you really need is the ability to set the value of the needle and the labels.

Here's an example. ( Make sure that you're running this in IE5.5+ or Firefox 2. The gauges won't show up in Safari ).

The two gauges respond to the mousemove event. The x or the e.clientX position and the y or the e.clientY position are updated on the gauges.

First, you'll need to get the gauges. Bindows has made this really easy. You need to download the gauge library and then follow these instructions.

Once you have them, you can manipulate the XML that defines the gauges. You can change the label, the start point, the end point, the ticks, etc.

However, those changes are static. What's more useful is to make changes at runtime. So, now, it's a matter of adding an event handler to manipulate the gauges.

In our example, we do two things --

1) Update the value of the needle with the new x/y position
2) Update the labels with the actual x/y position

The first is easy and is documented by Bindows. In our case, we do --

var xPosGauge = bindows.loadGaugeIntoDiv("gauges/gauge1.xml", "xPosGauge");
var yPosGauge = bindows.loadGaugeIntoDiv("gauges/gauge2.xml", "yPosGauge");

var xNeedle = xPosGauge.needle, yNeedle = yPosGauge.needle;

var handleMousemove = function(e) {
var x = parseInt(e.clientX);
...
xNeedle.setValue(x);
var y = parseInt(e.clientY);
...
yNeedle.setValue(y);
}

We get a reference to the gauge object, get the property "needle" and then at runtime ( in the mousemove event handler ) set it's value. The needle moves!

The second part is a little bit more difficult. I wasn't able to find a way to get to the labels without doing a "hack."

Through the magic of Firebug ( or Microsoft's Developer Toolbar ), we'll find out that the label is a BiGauge2Label.

Unfortunately, knowing that isn't really too useful. What's more useful is knowing where the second label appears in the structure of the gauge. By knowing that, we get a reference and can manipulate it.

This is why there's this strange looking code which locates the second label --

var xKids = xPosGauge.__gauge._gauge2Group.getChildren();
var len = xKids.length;
var ct = 0, xPxLabel, yPxLabel;

for (var i=0;i
if (xKids[i] instanceof BiGauge2Label) {
if(ct==1) {
xPxLabel=xKids[i];
break;
}
++ct;
}
}


Once we get the label, it's just a matter of setting it in the event handler --

var handleMousemove = function(e) {
var x = parseInt(e.clientX);
xNeedle.setValue(x);
xPxLabel.setText(x);
var y = parseInt(e.clientY);
yNeedle.setValue(y);
yPxLabel.setText(y);
}

The example uses the YUI Library to add the mousemove event, but you don't have to. You can use any library you want ( or not ) to handle the event.

To run my example, on your systems, you'll need --

1) The Bindows gauge library
2) My two gauges ( gauge1.xml and gauge2.xml )
3) My code ( and view the source )

That's it! Please update and improve. Enjoy!