surelyyourenotserious.com
Event Function Stacking in JavaScript

<disclaimer>I don’t usually post the intricacies of my work here, but this one was just too cool to keep to myself. Those of you who are not steeped in JavaScript, DOM, and XHTML can stop reading here and come back later for your normal dose of humor and/or world news.</disclaimer>

I’ve been struggling for a while now with a problem in IE. On my company’s website, we use the DOM and JavaScript perform a lot of CSS trickery. IE doesn’t support some of the CSS pseudo classes we use to decorate our forms and to create our drop-down navigation. The best way I’ve found to get these pseudo classes to work in IE is the suckerfish library. You can read more about how suckerfish accomplishes this at the son of suckerfish site. They explain it much better than I could here.

Suckerfish has been wonderful and we’ve used it all over our site. However, I ran into a snag. We’re adding a search form to the top of our page template, where real estate is premium. In order to save space, I created a small icon-like button to submit the form rather than using a bigger “Search” button.

Now, to be a good and accessible site, we need to explicitly label the form somehow so the user doesn’t have to infer what it’s for. My idea (admittedly stolen) was to have the default value of the field be "Search...". When the user clicks into the field, an onfocus event removes the default value, and if the user doesn’t give us a search term, the onblur event puts the default value back. Pretty straight forward, right? Wrong.

Suckerfish has this one drawback: It overwrites any preexisting event. When my suckerfish script added an onfocus event to my search box (to change the CSS style) it overwrote my clever text value swap. I needed a way for suckerfish to stack events instead of blindly assigning the event listener to a new function.

Another bit of code we use (to created rounded boxes) utilized Scott Andrew’s addEvent method. I tried switching suckerfish to use this, but the outcome was the same. The inline event functions were overwritten.

My good friend and colleague, Randy Peterman, sent me a link to a potential fix. Simon Wilson’s addLoadEvent method seemed to do exactly what I was looking for. And sure enough, I was able to stack my events by slightly modifying his window.onload logic to take the element object and the event type as arguments. Cloogy, yes. Uses eval method, yes. But worked, apparently, at least in testing. It wasn’t until I added my code to change the value of this that this method failed.

It took a me a while to really understand what Simon’s method was doing. It involves JavaScript Closures and, what I best understand as the scope of nested function objects. When using Simon’s closure method, the this object reference gets lost as the nesting of functions unwinds. So when I tried to nest…

if( this.value == 'search...')

…I found out that this was undefined. The complicated solution was just too complicated. I needed to dumb it down.

So I did just that: Make it elementary. Rather than storing function objects within function objects and nesting closures, I just needed to strip out the meat of the functions and tack them together. Sounds pretty sophomoric, but it solved my problem.

See a sample here.

/* Retains any existing event listener, rather than overwritting it. Trint Ladd */
function stackEvent( obj, eventType, func )
{
	var oldEvent = eval( "obj." + eventType );
	var oldInnerSource = "";
	var newInnerSource = func.toString()
	newInnerSource = newInnerSource.substring( newInnerSource.indexOf( "{" ) + 1, newInnerSource.lastIndexOf( "}" ) );

	if( typeof oldEvent == "function" )
	{
		oldInnerSource = oldEvent.toString()
		oldInnerSource = oldInnerSource.substring( oldInnerSource.indexOf( "{" ) + 1, oldInnerSource.lastIndexOf( "}" ) );
	}
	eval( "obj." + eventType + " = function(){ " + oldInnerSource + newInnerSource + " }" );
}

/* suckerfish focus on textboxes */
sfFocusInput = function( elements )
{
	for( var i=0; i < elements.length; i++ )
	{
		if( elements[i].tagName == "INPUT" && ( elements[i].type != "text" && elements[i].type != "password" ) )
		{
			// Don't apply this to other input types (buttons, radios, etc.)
			continue;
		}

		stackEvent( elements[i], "onfocus", function()
			{
				this.value += "1";
			}
		);
		stackEvent( elements[i], "onfocus", function()
			{
				this.value += "2";
			}
		);
		stackEvent( elements[i], "onfocus", function()
			{
				this.value += "3";
			}
		);
	}
}

/* suckerfish method can be found at http://www.htmldog.com/articles/suckerfish/ */
suckerfish(sfFocusInput, "INPUT");

© Copyright 2004-2005, Light-Spark Design
Powered By WordPress