How to create a New Custom JSF Component that randomly displays select items in SelectOne… and SelectMany components

Selection components, such as Radio Buttons. Dropdown-lists, Lists, Shuttles  and Check Boxes, are key elements of many a web page. Java Server Faces has a pretty wide range of such selection widgets available, from selectOneRadio to selectManyCheckbox in the RI and a wide range of additional components in distributions such as ADF Faces and MyFaces. The options to choose from are specified through SelectItem and SelectItems elements. For example:

<h:selectOneRadio id="colorchooser" layout="pageDirection">
<f:selectItem itemLabel="Brown" itemValue="Brown"/>
<f:selectItem itemLabel="Red" itemValue="Red"/>
<f:selectItem itemLabel="Blue" itemValue="Blue"/>
<f:selectItem itemLabel="Yellow" itemValue="Yellow"/>
<f:selectItem itemLabel="Green" itemValue="Green"/>
</h:selectOneRadio>
 

The options are presented to the user in the order in which they are defined in the SelectItem and SelectItems elements. In most situations that is perfectly alright or even very desirable. However, there are situations where we might prefer to apply a random ordering to the options in the selection component. For example in Polls we would either not want to influence the poll result through presenting options in a specific order to every interviewee. And in Exams/tests we might want to minimize the risk of candidates informing each other on the correct answers by presenting the answers in an ever changing sequence.

The challenge I will face in this article is the implementation of a new JSF Component that will reshuffle the order of the select items. All we need to do in order to  make a  Select Component display its options in a random sequence is apply this new component.....

When I first started thinking about the implementation of randomly organized dropdown-lists and radio buttons, I considered at least four options for making the approach:

  1. randomize at the model level – at the source of the select items (provided of course the selectItems elements in powered by some backing bean)
  2. client-side randomize the rendered elements on load in the browser using JavaScript
  3. create my own components as alternative for the RI’s selectOneRadio, selectOneMenu, selectManyCheckbox etc.
  4. create a new component that can wrap the selection components such as selectOneRadio and somehow mess up there children
  5. create a new component that can wrap the selectItem and selectItems child elements of the selection components like selectOneRadio, selectManyList etc.

The first option was way too intrusive, could not distinguish between randomizing per session and per request, would not be able to deal with select options derived from multiple collections (multiple selectItems), so I could quickly discard it. The second one is incredibly messy and still required some server side component as well. It too was rapidly abandoned. Creating alternatives to selectOneRadio etc. – the third option – is a possibility, but would be my last resort as clearly it involves an awful lot of work and duplication of existing code and functionality. The fourth option is perhaps feasible, but not in a very simple way. I could pass the child selection component my own writer to render to and then engineer the html written to that writer object before sending it to the ‘real’ response writer. Again, not an attractive approach.

Then the last option presented itself and appeared to be a quite elegant one: we create a component that can be used like a regular selectItems component on the one hand, and therefore can be enclosed inside any select component, while on the other hand it can envelope selectItem and selectItems elements, retrieve and combine all their selectItem and pass them through to the parent selection component in a randomized order. This new component can be used in a JSF page like this:

<h:selectOneRadio id="colorchooser" layout="pageDirection">
<AMISJSF:randomizeSelectItems>
<f:selectItem itemLabel="Blue" itemValue="Blue"/>
<f:selectItem itemLabel="Yellow" itemValue="Yellow"/>
<f:selectItem itemLabel="Green" itemValue="Green"/>
</AMISJSF:randomizeSelectItems>
</h:selectOneRadio>
 

It is easy to use: simply inject it between the selection component and its selection items. It can be used with every selection component that uses selectItems:

<h:selectManyCheckbox id="colorchooser" layout="pageDirection">
<AMISJSF:randomizeSelectItems>
<f:selectItem itemLabel="Blue" itemValue="Blue"/>
<f:selectItem itemLabel="Yellow" itemValue="Yellow"/>
<f:selectItem itemLabel="Green" itemValue="Green"/>
</AMISJSF:randomizeSelectItems>
</h:selectManyCheckbox>

And with each render-operation, it will reshuffle the selection options. Note that we can have a combination of randomly shuffled elements and select items in a fixed order:

<h:selectManyCheckbox id="colorchooser" layout="pageDirection">
<f:selectItem itemLabel="Brown" itemValue="Brown"/>
<f:selectItem itemLabel="Red" itemValue="Red"/>
<AMISJSF:randomizeSelectItems>
<f:selectItem itemLabel="Blue" itemValue="Blue"/>
<f:selectItem itemLabel="Yellow" itemValue="Yellow"/>
<f:selectItem itemLabel="Green" itemValue="Green"/>
</AMISJSF:randomizeSelectItems>
</h:selectManyCheckbox>

How to create a New Custom JSF Component that randomly displays select items in SelectOne... and SelectMany components randomSI cb1How to create a New Custom JSF Component that randomly displays select items in SelectOne... and SelectMany components randomSI cb2 

We can also use the randomizeSelectItems element with the ADF Model. See for example this snippet that renders a dropdown list of employees in a random order:

<af:selectOneChoice >
<AMISJSF:randomizeSelectItems>
<f:selectItems value="#{bindings.EmpView1Ename.items}"/>
</AMISJSF:randomizeSelectItems>
</af:selectOneChoice>

How to create a New Custom JSF Component that randomly displays select items in SelectOne... and SelectMany components randomSI emp2 after refreshHow to create a New Custom JSF Component that randomly displays select items in SelectOne... and SelectMany components randomSI emp1

Let’s take a look at the implementation of this new component.

The class implementing the RandomizeSelectItems component extends the UISelectItems class in the javax.faces.component package. Its getValue() method returns a Collection of selectItems. The class gets hold of the child selectItem and selectItems elements through the this.getChildren() call. It builds a list of all selectItems, using the helper class SelectItemSupport. Note that this is a class in the ADF Faces library, so we have created a dependency there. Also note that we could easily implement the functionality of SelectItemSupport ourselves. After building the entire collection, we apply the simplest way of randomizing the order of the collection’s elements: by using the Collections.shuffle() method.

package nl.amis.jsf;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import javax.faces.component.UISelectItem;
import javax.faces.component.UISelectItems;
import javax.faces.context.FacesContext;
import javax.faces.model.SelectItem;

import oracle.adfinternal.view.faces.renderkit.uix.SelectItemSupport;

/**
* Class has to extend UISelectItems as supporting classes (such as javax.faces.component.SelectItemsIterator, SelectItemSupport)
* used by UISelectOne and other selection components check explicitly for children
* that are an instance of that class (if(kid instanceof UISelectItems)...)
*
*/
public class RandomizeSelectItems extends UISelectItems {
public RandomizeSelectItems() {
setRendererType(null);
}

public String getFamily() {
return COMPONENT_FAMILY;
}

public Object getValue() {
List kids = this.getChildren();
int childCount = this.getChildCount();
List<SelectItem> siList = new ArrayList();
for (int i = 0; i < childCount; i++) {
if (kids.get(i) instanceof UISelectItem) {
SelectItemSupport.addSelectItem((UISelectItem)kids.get(i),
siList);
} else if (kids.get(i) instanceof UISelectItems) {
SelectItemSupport.addSelectItems((UISelectItems)kids.get(i),
siList);
}
}
Collections.shuffle(siList);
return siList;
}

public static final String COMPONENT_TYPE = "RandomizeSelectItems";
public static final String COMPONENT_FAMILY = "javax.faces.SelectItems";

}
 

Other steps in implementing my new JSF Component:

  1. implement Tag Class
  2. add description to TLD
  3. register component in faces-config
  4. package and distribute
  5. use in new JSF applications

1. Implement Tag Class

A very simple class, as this tag has not attributes:

package nl.amis.jsf.tags;

import javax.faces.application.Application;
import javax.faces.component.UIComponent;
import javax.faces.context.FacesContext;
import javax.faces.el.ValueBinding;
import javax.faces.webapp.UIComponentTag;

public class RandomizeSelectItemsTag extends UIComponentTag {

public RandomizeSelectItemsTag() {
}

protected void setProperties(UIComponent component) {
super.setProperties(component);
}

public void release() {
super.release();
}


public String getComponentType() {
return "RandomizeSelectItems";
}

public String getRendererType() {
return null;
}
}
 

2. Add description to Tag Library Description

  <tag>
<description>Randomize the order of display of the enclosed select items</description>
<display-name>Randomize Select Items</display-name>
<name>randomizeSelectItems</name>
<tag-class>nl.amis.jsf.tags.RandomizeSelectItemsTag</tag-class>
<body-content>JSP</body-content>
<example>Brief snippet showing how to use this tag.</example>
</tag>

3. Register component in faces-config

  <component>
<component-type>RandomizeSelectItems</component-type>
<component-class>nl.amis.jsf.RandomizeSelectItems</component-class>
</component>
 

4. Package and Distribute

See  previous article on Deploying, Packaging and Distributing Custom JSF Components for details.

5. Use the new Custom JSF Component in JSF Applications

Also see the article referenced above. And see the snippets at the start of this article. Note that any combination of SelectItem and SelectItems can be nested inside a randomizeSelectItems tag. That tag itself is treated as if it were a SelectItems tag by its parent.

Further Steps

While we may want to present the select-options in random order to each new interviewee, candidate or contestant, we probably want to present them in the same order for every new display in the same session for the same user. So we should add to the new component a way of having it randomize for every request or for every session.

Resources

Accessible and pretty complete Tutorial on creating new JSF component  http://www.exadel.com/tutorial/jsf/HowToWriteYourOwnJSFComponents.pdf  

One Response

  1. Manda Sriram February 12, 2007