« ADF Faces: Showing Reports | Main | ADF Faces: Using af:poll to refresh a table »
ADF Faces: How-to smart filter the af:SelectOneChoice with JavaScript
By frank.nimphius | September 14, 2007
The af:selectOneChoice component in ADF Faces shows a dropdown list for users to choose a value that then is added as a value to the target form attribute. Putting focus on the selectOneChoice component and then pressing e.g. the "S" key shows the first list item that starts with a "S". Pressing a subsequent "A" shows the first list entry that starts with an "A". However, what if you wanted to jump to all entries that start with "Sa", or "Co" ?
![]() |
This usecase doesn't work with the default functionality of the af:selectOneChoice component but can be added in a generic way using JavaScript on the page. The solution I am blogging here works on Firefox and IE with multiple instances of af:selectOneChoice on a page. The part that cost me the most time developing is the solution to the popular Shakespears question for IT geeks: "IE or not IE?" In other words, solving the browser differences. However, the Internet is full of hints and tips and John Resig's book about JavaScript techniques brought me onto the right path. Still, if you are a proven JavaScript propeller head, please forgive me that I didn't drive unobtrusive JavaScript to its perfection. Its a sample - though a good one as I think.
Implementing this solution and pressing "Sa" on the af:selectOneChoice component, filters the list as shown below
![]() |
To re-build the original list, just hit the ESC key while the af:selectOneChoice component is in focus. Note that this also work if you edit a second af:selectOnceChoice list after editing the first and then you come back to the first to recover the original values.
Implementation Details
The JavaScript needs to be added to the ADF Faces page source as follows:
-
<afh:html>
-
<afh:head title="IntelligentLov">
-
<meta http-equiv="Content-Type"
-
content="text/html; charset=windows-1252"/>
-
<script type="text/javascript">
-
-
.... JavaScript goes here ....
-
-
</script>
-
</afh:head>
Before showing the JavaScript code and explaining some of its details, let continue with the page set-up. On page load, the registration of the af:selectOneChoice components needs to be performed. This can be done by adding the following onLoad event handler
-
<afh:body onload="registerComponentToEvent();">
The last configuration you need to do on the page is to provide ID values to the selectOneChoice components and the surrounding form element. This is used to create the select element's ID at runtime.
-
<h:form id="main">
-
...
-
<af:selectOneChoice value="#{bindings.EmployeesView1ManagerId.inputValue}"
-
label="#{bindings.EmployeesView1ManagerId.label}"
-
id="manager">
-
<f:selectItems value="#{bindings.EmployeesView1ManagerId.items}"/>
-
</af:selectOneChoice>
This will create the runtime ID of "main:manager". Note that using af:form does not add the form's ID to the component (you can check the generated actual component ID in the page source at runtime in case I confused you).
The JavaScript code is generic, except for the registerComponentToEvent method, which needs to add the selectOneChoice coponent ID so the javaScript event handler can be created at runtim
-
function registerComponentToEvent(){
-
-
// THIS BLOCK NEEDS TO BE FOR EACH LOV
-
var comp = document.getElementById("main:choice");
-
registerEvent(comp,"keydown",keyin,true);
-
-
userString[0].component_id = comp.id;
-
userString[0].string="";
-
-
storedOptions[0].component_id = comp.id;
-
storedOptions[0].optionElements;
-
//END BLOCK - main:choice
-
-
-
// THIS BLOCK NEEDS TO BE FOR EACH LOV
-
var comp = document.getElementById("main:manager");
-
registerEvent(comp,"keydown",keyin,true);
-
-
userString[1].component_id = comp.id;
-
userString[1].string="";
-
-
storedOptions[1].component_id = comp.id;
-
storedOptions[1].optionElements;
-
//END BLOCK - main:manager
-
-
-
}
The snippet above registers two selectOneChoice components"main:choice" and "main:manager". Note that the "main:manager" component uses the index 1 while "main:choice" uses 0. This is needed for the multi instance support on a single page. If you added another af:selectOnceChoice component then the index for this would be 2 and so on.
There is one more code line of interest
-
if ((e.keyCode>64 && e.keyCode<91)
-
||(e.keyCode>48 && e.keyCode<57)
-
||e.keyCode==32){ ... }
This line allows all character and number keys + the blank key as filter criteria. If your lists contain special charaters, then the key code for those need to be added to the list.
The ESC key is a reserved key and used to recover the original list so that this solution doesn't become a one-way street.
The full JavaScript code:
-
<script type="text/javascript">
-
-
-
function keyin(e){
-
//Shakespear for IT Geeks: IE or not IE, that is the question.
-
var comp = e.target || e.srcElement;
-
var indx = 0;
-
-
for(i=0;i<userString.length;++i){
-
if (userString[i].component_id == comp.id){
-
indx=i;
-
break;
-
}
-
}
-
-
//store original list to recover
-
if (storedOptions[indx].optionElements == null){
-
for(i=0;i<comp.options.length;++i){
-
storedOptions[indx].optionElements[i]=comp.options[i];
-
}
-
}
-
-
// all characters and blanks welcome
-
if ((e.keyCode>64 && e.keyCode<91)
-
||(e.keyCode>48 && e.keyCode<57)
-
||e.keyCode==32){
-
-
var charTyped = eval("String.fromCharCode("+e.keyCode+")");
-
userString[indx].string=userString[indx].string+charTyped.toLowerCase();
-
var arraylength = comp.options.length;
-
for (i=arraylength-1; i> -1; --i){
-
var currentOption = comp.options[i].text.toLowerCase();
-
if(currentOption.indexOf(userString[indx].string)!= 0){
-
comp.removeChild(comp.options[i]);
-
}
-
}
-
}
-
//ESC to reset list
-
else if (e.keyCode == 27){
-
resetLov(comp,indx);
-
userString[indx].string="";
-
}
-
}
-
-
function resetLov(element, indx){
-
var comp = element;
-
if (storedOptions[indx].optionElements!=null){
-
for (i=0; i<storedOptions[indx].optionElements.length;i++){
-
comp.appendChild(storedOptions[indx].optionElements[i]);
-
}
-
}
-
}
-
-
function registerEvent(obj,eventType,_function,useCaption){
-
//Shakespear for IT Geeks: IE or not IE, that is the question.
-
if (obj.addEventListener){
-
obj.addEventListener(eventType,_function,useCaption);
-
return true;
-
} else if (obj.attachEvent){
-
return obj.attachEvent('on'+eventType,_function);
-
}
-
else{
-
return false;
-
}
-
}
-
-
function registerComponentToEvent(){
-
-
// THIS BLOCK NEEDS TO BE FOR EACH LOV
-
var comp = document.getElementById("main:choice");
-
registerEvent(comp,"keydown",keyin,true);
-
-
userString[0].component_id = comp.id;
-
userString[0].string="";
-
-
storedOptions[0].component_id = comp.id;
-
storedOptions[0].optionElements;
-
//END BLOCK - main:choice
-
-
-
// THIS BLOCK NEEDS TO BE FOR EACH LOV
-
var comp = document.getElementById("main:manager");
-
registerEvent(comp,"keydown",keyin,true);
-
-
userString[1].component_id = comp.id;
-
userString[1].string="";
-
-
storedOptions[1].component_id = comp.id;
-
storedOptions[1].optionElements;
-
//END BLOCK - main:manager
-
-
-
}
-
-
</script>
Download a JDeveloper 10.1.3.3 Example
The following JDevelper 10.1.3.3 workspace contains the solution introduced in this blog. Run the "browseDepartments.jsp" which in fact is a browse Employees page (my fault) and click the "Create" button to create a new employee. Navigate through the form and filter the two select boxes as you please.
- Download the JDeveloper 10.1.3.3 workspace
- Copy adf-faces-impl.jar from jdeveloper_10133\jlib to IntelligentLOV\ViewController\public_html\WEB-INF\lib
- Copy jsf-impl.jar from jdeveloper_10133\jsf-ri to IntelligentLOV\ViewController\public_html\WEB-INF\lib
- Create a named database connection hrconn in JDeveloper
- Run browseDepartments.jsp
- Create a new Employee by pressing the "Create" button and step over the lists. Press e.g. "s" key and then "a" to see all list items starting with "Sa". Hit "ESC-key to rebuild the full list"
Have fun !
Frank
Topics: ADF Faces | No Comments »
Comments are closed.


