CSS selectors is a powerful weapon for any web-developer, but cross-browser compatibility issues could reduce advantages in real world usage. Thankfully, Prototype 1.5.1 supports almost all CSS3 selectors programmatically. This means CSS3 selectors can be used in any modern browser.
In this article I want to show how a particular method has been evolved during Modalbox development. This is the internal OO-method which is being used for searching for DOM elements located inside the Modalbox window, which can gain user focus. These elements include form elements and links: input
, select
, textarea
, button
, a
and they are being used for looping the TAB-key focusing inside the modal dialog.
By the way, the new version of the Modalbox is supporting also the Shift+TAB
keystroke for backward focus movement.
Let me show a solution I came to initially. Here it is:
_findFocusableElements: function(){ // Collect form elements or links from MB content
return $A($("MB_content").descendants()).findAll(function(node){
return (["INPUT", "TEXTAREA", "SELECT", "A", "BUTTON"].include(node.tagName));
});
}
This solution is worked well, but recently I got a bug report considering hidden input elements. As you can note, the above solution will include all input elements into the list of “focusable” elements and once you try to set focus into the hidden field pragmatically you’ll get a javascript error. Since we can’t afford this I decided to rewrite this method from scratch in 1.5.5.
Here is that I created after some thoughts and trials:
_findFocusableElements: function() {
var els = this.MBcontent.getElementsBySelector('input:not([type~=hidden]), select, textarea, button, a[href]');
els.invoke('addClassName', 'MB_focusable');
return this.MBcontent.getElementsByClassName('MB_focusable');
}
You might wonder why I used additional class names and called getElements…
functions for 2 times? Well, as you can read from prototype’s API documentation getElementsBySelector
method should result with document-ordered array of elements, but for some reason it behaves not as stated. So I decided to create a temporary solution with a class name attribute involved.
As this ticket will be resolved the method could look like:
_findFocusableElements: function() {
return this.MBcontent.getElementsBySelector('input:not([type~=hidden]), select, textarea, button, a[href]');
}
This is just a simple example how the particular part of code can be improved with strengths of Prototype and CSS-selectors making it possible to pack really complex expressions into just one line of code.