Dom.Activate to Focus on Form Elements

One of the methods that I missed when switching from Prototype to YUI, was a generic method to use to focus on a form element and/or selecting its text. Each browser supports focus in a slightly different way and until recently most did not support the select Function on an Input Element. In addition, you should never attempt to focus on a hidden or non-displayed Element, nor should you focus on an Input Element of type hidden. Because of all this trickiness, it is rather handy to have one method that manages all this for you.

Example 1: Dom.Activate

Dom.activate = function(elem, noSelect) {
	var node = Dom.get(elem),
		dim = Dom.getRegion(node);

	if (hidden !== node.type && (dim.top || dim.left || dim.bottom || dim.right)) {
		setTimeout(function() {node.focus();}, 1);
		if (isType(node.select, function) && ! noSelect) {setTimeout(function() {node.select();}, 1);}
		return true;
	}
	
	return false;
};

As usual, I am assuming that you have created a shortcut for "YAHOO.util.Dom" as "Dom". The parameters are: 1) the string Id of the element or JavaScript pointer thereto, 2) optional parameter that prevents text selection when true. We first verify that the Element is not the hidden type and that it is visible by checking that the Element has dimensions. Simply checking the display style isnt enough, because styles applied by classes do not set the style property of the Element. Next we call "node.focus()" and "node.select()", if the method exists and noSelect is falsy. Now the trickiest issue this method solves is an event timing problem with FireFox on the Mac, Bug 53579, which causes the focus to not work properly. We solve the bug by calling the focus and select methods inside a one millisecond timeout. Lastly, we return whether the focus was successful or not.

Now that we understand the method, but what is a good application of it? How about focusing on the first Input inside of a Form? Here is a method does just that:

Example 2: Dom.focusOnFirstElement

Dom.focusOnFirstElement: function(form) {
	var node = null;

	Core.batch(form.getElementsByTagName(input), function(npt) {
		if (Dom.activate(npt)) {
			node = npt;
			return;
		}
	});

	return node;
};

Using the Batch Function we iterate on the Input Elements of a Form Element and then attempt to active each one in order. The first time we are successful, we stop the iterate and then return the newly focused Input, just in case you want to use it. I use the "Dom.activate" method most frequently with this type of operation, with tab key management being a distant second.

The biggest failure of this method, is that, as it is right now, it does not support TextArea and Select Elements. If you dont care about those Elements, then you are good to go. If you do, then the best method, I know of, is to use getElementsByTagName for each type and join them together, before iterating. However, then you have to deal with figuring out the actual order of the Elements in the DOM, and it is rather slow. Basically, its a mess and probably not worth your time, unless you know something of something slick. If you know of a good method for fetching multiple element, in the proper order (as they appear in the DOM), please post it in the comments.