Hi I've this situation and I dont know why it's happening..
I have a selectonemenu like this
<ice:selectOneMenu id="ddlProfesion" value="#{FrmClientes.profesionSeleccionado}" style="width:230px">
<f:selectItems value="#{SessionBean1.listaProfesion}"/>
<f:converter converterId="DefaultSelectItemConverter" />
</ice:selectOneMenu>
the list of items
public List getListaProfesion() {
if (listaProfesion == null) {
Session session = HibernateUtil.getSessionFactory().getCurrentSession();
session.beginTransaction();
listaProfesion = new ArrayList<SelectItem>();
List<Profesion> profesionList = session.getNamedQuery("Profesion.findAll").list();
for (Profesion c : profesionList) {
listaProfesion.add(new SelectItem(c, c.getNombre()));
}
return listaProfesion;
}
return listaProfesion;
}
now I have a datatable and when I click in a row a panelPopup open with the data of the object Profesion..
the code of the selectionListener in the rowSelector is this:
public void seleccionaTerceros(RowSelectorEvent event) {
Session session = HibernateUtil.getSessionFactory().getCurrentSession();
session.beginTransaction();
Query query = session.getNamedQuery("Clientes.findByTercero");
query.setParameter("tercero", "12332454");
// I send a parameter value for example
if (!query.list().isEmpty()) {
cliente = (Clientes) query.list().get(0);
profesionSeleccionado=cliente.getProfesionID();
} else {
cliente = null;
profesionSeleccionado=null;
}
setMostrarModal(true);
}
I set profesionSeleccionado to the Value of the objetc and doesnt work, I put this code in another location, like the constructor of the managed bean or in a button action.. and IT WORKS...
I have debbuged and see that the getter and the setter of the atribute are accesed twice, why, i dont know
please I need some guide, I'am new with this.. Thanks
pd: the code of the converter used to list objects in the selectonemenu is this
public class DefaultSelectItemConverter implements Converter {
/**
* Not explicitly documented.
*
* #see javax.faces.convert.Converter#getAsObject(javax.faces.context.FacesContext,
* javax.faces.component.UIComponent, java.lang.String)
*/
public Object getAsObject(FacesContext fcontext, UIComponent comp, String valueString) {
List<UIComponent> children = comp.getChildren();
for (UIComponent child : children) {
if (child instanceof UISelectItem) {
UISelectItem si = (UISelectItem) child;
if (si.getValue().toString().equals(valueString)) {
return si.getValue();
}
}
if (child instanceof UISelectItems) {
UISelectItems sis = (UISelectItems)child;
List<SelectItem> items = (List)sis.getValue();
for (SelectItem si : items) {
if (si.getValue().toString().equals(valueString)) {
return si.getValue();
}
}
}
}
throw new ConverterException("no conversion possible for string representation: " + valueString);
}
/**
* Not explicitly documented.
*
* #see javax.faces.convert.Converter#getAsString(javax.faces.context.FacesContext,
* javax.faces.component.UIComponent, java.lang.Object)
*/
public String getAsString(FacesContext fcontext, UIComponent comp, Object value) {
return value.toString();
}
}
I found the solution in this page
http://jira.icefaces.org/browse/ICE-2297
the problem was corrected putting immediate="false" on the RowSelector
:)
Related
I've written a custom converter as follows:
#FacesConverter(value = "orderListConverter")
public class OrderListConverter implements Converter {
#Override
public Object getAsObject(FacesContext context, UIComponent component, String value) {
Object ret = null;
if (component instanceof OrderList) {
Object list = ((OrderList) component).getValue();
ArrayList<ExampleEntity> al = (ArrayList<ExampleEntity>) list;
for (Object o : al) {
String name = "" + ((ExampleEntity) o).getName();
if (value.equals(name)) {
ret = o;
break;
}
}
}
return ret;
}
#Override
public String getAsString(FacesContext context, UIComponent component, Object value)
{
String str = "";
if (value instanceof ExampleEntity) {
str = "" + ((ExampleEntity) value).getNumber();
}
return str;
}
}
My ExampleEntity is implemented as follows:
public class ExampleEntity {
private String name;
private int number;
public ExampleEntity(String name, int number) {
this.name = name;
this.number = number;
}
#Override
public String toString() {
return "toString(): [name=" + name + ", number=" + number + "]";
}
public String getName() {
return this.name;
}
public void setName(String name) {
this.name = name;
}
public int getNumber() {
return number;
}
public void setNumber(int number) {
this.number = number;
}
}
the orderList-Component from Primefaces looks like that:
<p:orderList value="#{orderListBean.exampleList}"
var="exampleEntity" itemValue="#{exampleEntity}"
converter="orderListConverter">
<p:column style="width:25%">
#{exampleEntity.number}
</p:column>
<p:column style="width:75%;">
#{exampleEntity.name}
</p:column>
</p:orderList>
and the bean is implemented as follows:
#SessionScoped
#ManagedBean(name = "orderListBean")
public class OrderListBean {
private List<ExampleEntity> exampleList;
#PostConstruct
public void init() {
exampleList = new ArrayList<ExampleEntity>();
exampleList.add(new ExampleEntity("nameOne", 1));
exampleList.add(new ExampleEntity("nameTwo", 2));
exampleList.add(new ExampleEntity("nameThree", 3));
exampleList.add(new ExampleEntity("nameFour", 4));
exampleList.add(new ExampleEntity("nameFive", 5));
}
public List<ExampleEntity> getExampleList() {
return exampleList;
}
public void setExampleList(List<ExampleEntity> exampleList) {
this.exampleList = exampleList;
}
}
1) when debugging, the value Parameter of getAsObject() contains
the number of the ExampleEntity, but I had expected the
toString() method of ExampleEntity to be called!
2) What is the correct content for itemValue attribute?
Is it a kind of convention over configuration? Or how does the component
'know', to use the whole object, when inserting exampleEntity into itemValue
Hope everything is clear! Tanks a lot for any explanation!
Converters basically serve to transform values in 2 directions:
Server to client, when the value is rendered.
Client to server, when the value is submitted.
In your getAsString you established, that the string representation, the one which client uses, is exampleEntity's number. So that's what gets rendered to client as a value. And later, when the client submits its value, that value is number. To convert it to the object (server) representation, the getAsObject called with the number as a parameter.
The server can't possibly call getAsObject with exampleEntity.toString(), because it doesn't have the exampleEntity instance at that point, only the submitted number.
To illustrate, this should hold:
obj.equals(conv.getAsObject(ctx, comp, conv.getAsString(ctx, comp, obj)));
getAsObject and getAsString should be inversive in their input and output.
To answer your 2nd question: that depends on your needs. You could say itemValue="#{exampleEntity.number}", but that would make sense only if you're not interested in the exampleEntity itself, i.e. on submit you would get the number from the client and that's all you need for your server-side logic.
#FacesConverter(value = "EntityConverter")
public class EntityConverter implements Converter, Serializable {
private static final long serialVersionUID = 1L;
public EntityConverter() {
super();
}
#Override
public Object getAsObject(final FacesContext context, final UIComponent component, final String value) {
if (value == null) {
return null;
}
return fromSelect(component, value);
}
/**
* #param currentcomponent
* #param objectString
* #return the Object
*/
private Object fromSelect(final UIComponent currentcomponent, final String objectString) {
if (currentcomponent.getClass() == UISelectItem.class) {
final UISelectItem item = (UISelectItem) currentcomponent;
final Object value = item.getValue();
if (objectString.equals(serialize(value))) {
return value;
}
}
if (currentcomponent.getClass() == UISelectItems.class) {
final UISelectItems items = (UISelectItems) currentcomponent;
final List<Object> elements = (List<Object>) items.getValue();
for (final Object element : elements) {
if (objectString.equals(serialize(element))) {
return element;
}
}
}
if (!currentcomponent.getChildren().isEmpty()) {
for (final UIComponent component : currentcomponent.getChildren()) {
final Object result = fromSelect(component, objectString);
if (result != null) {
return result;
}
}
}
if (currentcomponent instanceof OrderList) {
Object items = ((OrderList) currentcomponent).getValue();
List<Object> elements = (List<Object>) items;
for (final Object element : elements) {
if (objectString.equals(serialize(element))) {
return element;
}
}
}
return null;
}
/**
* #param object
* #return the String
*/
private String serialize(final Object object) {
if (object == null) {
return null;
}
return object.getClass() + "#" + object.hashCode();
}
#Override
public String getAsString(final FacesContext arg0, final UIComponent arg1, final Object object) {
return serialize(object);
}
}
i have a problem with conversion of entitys with a version. I made a simple example to explain my problem because the "real" application is to big and contains many unnecessary things.
Situation: I have a web application with primefaces and openjpa. I have 20 components (autocompletes + selectedmenues) that needs a converter and they use persistence entitys.
Informations: I only want use jsf,primefaces for it! (Nothing special like omnifaces or something else.) The Question is at bottom. This is only test-code. It is NOT complete and there are some strange things. But this explain my problem at best.
Example entity: (Only fields and hashcode + equals)
#Entity
public class Person {
#Id
private Long id;
private String name;
#Version
private Long version;
#Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((name == null) ? 0 : name.hashCode());
return result;
}
#Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Person other = (Person) obj;
if (name == null) {
if (other.name != null)
return false;
} else if (!name.equals(other.name))
return false;
return true;
}
}
First solution: My first solution was that i make a own converter for every component.I inject my managed bean there and use the getter from the "value" of the component.
Bean
#ManagedBean(name = "personBean")
#ViewScoped
public class PersonBean implements Serializable {
private List<Person> persons;
/** unnecessary things **/
xhtml:
<p:selectOneMenu >
<f:selectItems value="#{personBean.persons}"/>
</p:selectOneMenu>
converter:
#ManagedBean
#RequestScoped
public class PersonConverter implements Converter{
#ManagedProperty(value = "personBean")
private PersonBean personBean;
#Override
public Object getAsObject(FacesContext context, UIComponent component,
String value) {
//null & empty checks
Long id = Long.valueOf(value);
for(Person person : personBean.getPersons()){
if(person.getId().equals(id)){
return person;
}
}
throw new ConverterException("some text");
}
#Override
public String getAsString(FacesContext context, UIComponent component,
Object value) {
//null & Instanceof checks
return String.valueOf(((Person)value).getId());
}
}
Summary: This solution works good. But i found that there must be a better solution as an converter for every component.
Second solution: I found here on stackoverflow the Global Entity Converter. One converter for all, i thought that was a good solution. ("p:autocomplete for a global Entity Converter"). I use it and i thought it works fine. BUT after a few tests i found another big problem, the version of the entity.
Problem1 with entity converter:
I have the version field not in my hashcode or equals (i found nothing about it). I only read this (The JPA hashCode() / equals() dilemma) about it. The problem is that the entity will not replaced in the hashmap and in some cases i get an optimistic locking exception because the "old" entity stays in the hashmap.
if (!entities.containsKey(entity)) {
String uuid = UUID.randomUUID().toString();
entities.put(entity, uuid);
return uuid;
} else {
return entities.get(entity);
}
Solution: I thought that i can resolve this problem by adding an interface to my entitys that checks whether a version exists.
Interface:
public interface EntityVersionCheck {
public boolean hasVersion();
}
Implementation:
#Override
public boolean hasVersion() {
return true;
}
Converter:
#Override
public String getAsString(FacesContext context, UIComponent component, Object entity) {
synchronized (entities) {
if(entity instanceof EntityVersionCheck && ((EntityVersionCheck)entity).hasVersion()){
entities.remove(entity);
}
if (!entities.containsKey(entity)) {
String uuid = UUID.randomUUID().toString();
entities.put(entity, uuid);
return uuid;
} else {
return entities.get(entity);
}
}
}
This solution works for the optimistic locking exception but brings another problem!
Problem2 with entity converter:
<p:selectOneMenu value="#{organisation.leader}">
<f:selectItems value="#{personBean.persons}"/>
</p:selectOneMenu>
If a organisation has already a leader. It will be replaced with a new uuid if the leader is in the persons - list, too. The leader will be set to null or convert exception because the uuid does not exists anymore in the hashmap. It means he use the converter for the organisation.leader and add the leader to the hashmap. Than comes the persons- List and add all other persons in a hashmap and override the uuid from the organisation.leader if he exists in persons, too.
Here are two cases now:
When i select a other leader, it works normally.
If i dont change the "current" selection and submit the organisation.leader tries to find his "old" uuid but the other person from the person list has override it and the uuid does not exists and the organisation.leader is null.
I found another solution for it and this is my final solution BUT i find, that is a very very strange solution and i will do this better but i found nothing about it.
Final Solution
I add the "old" uuid to the "new" object.
#Override
public String getAsString(FacesContext context, UIComponent component,
Object entity) {
synchronized (entities) {
String currentuuid = null;
if (entity instanceof EntityVersionCheck
&& ((EntityVersionCheck) entity).hasVersion()) {
currentuuid = entities.get(entity);
entities.remove(entity);
}
if (!entities.containsKey(entity)) {
if (currentuuid == null) {
currentuuid = UUID.randomUUID().toString();
}
entities.put(entity, currentuuid);
return currentuuid;
} else {
return entities.get(entity);
}
}
}
Question: How i make this better and right?
If Solution 1 worked and you just want it more generic:
Holding your instances within scope of a bean you can use a more generic converter by removing the managed-bean lookup from it. Your entities should inherit from a base entity with a identifier property. You can instantiate this converter in your bean where you retrieved the entities.
Or use a guid map or public identifier if id should not be exposed in html source.
#ManagedBean(name = "personBean")
#ViewScoped
public class PersonBean implements Serializable {
private List<Person> persons;
private EntityConverter<Person> converter;
// this.converter = new EntityConverter<>(persons);
}
<p:selectOneMenu converter="#{personBean.converter}">
<f:selectItems value="#{personBean.persons}"/>
</p:selectOneMenu>
Abstract Base Entity converter:
/**
* Abstract Entity Object JSF Converter which by default converts by {#link Entity#getId()}
*/
public abstract class AEntityConverter<T extends Entity> implements Converter
{
#Override
public String getAsString(final FacesContext context, final UIComponent component, final Object value)
{
if (value instanceof Entity)
{
final Entity entity = (Entity) value;
if (entity.getId() != null)
return String.valueOf(entity.getId());
}
return null;
}
}
Cached collection:
/**
* Entity JSF Converter which holds a Collection of Entities
*/
public class EntityConverter<T extends Entity> extends AEntityConverter<T>
{
/**
* Collection of Entity Objects
*/
protected Collection<T> entities;
/**
* Creates a new Entity Converter with the given Entity Object's
*
* #param entities Collection of Entity's
*/
public EntityConverter(final Collection<T> entities)
{
this.entities = entities;
}
#Override
public Object getAsObject(final FacesContext context, final UIComponent component, final String value)
{
if (value == null || value.trim().equals(""))
return null;
try
{
final int id = Integer.parseInt(value);
for (final Entity entity : this.entities)
if (entity.getId().intValue() == id)
return entity;
}
catch (final RuntimeException e)
{
// do something --> redirect to exception site
}
return null;
}
#Override
public void setEntities(final Collection<T> entities)
{
this.entities = entities;
}
#Override
public Collection<T> getEntities()
{
return this.entities;
}
}
Remote or database lookup Converter:
/**
* Entity Object JSF Converter
*/
public class EntityRemoteConverter<T extends Entity> extends AEntityConverter<T>
{
/**
* Dao
*/
protected final Dao<T> dao;
public EntityRemoteConverter(final EntityDao<T> dao)
{
this.dao = dao;
}
#Override
public Object getAsObject(final FacesContext context, final UIComponent component, final String value)
{
// check for changed value
// no need to hit database or remote server if value did not changed!
if (value == null || value.trim().equals(""))
return null;
try
{
final int id = Integer.parseInt(value);
return this.dao.getEntity(id);
}
catch (final RuntimeException e)
{
// do someting
}
return null;
}
}
I use dao approach whenever I have to convert view-parameters and bean was not constructed yet.
Avoid expensive lookups
In dao approach you should check if the value has changed before doing potential expensive lookups, since converters could be called multiple times within different phases.
Have a look at source of: http://showcase.omnifaces.org/converters/ValueChangeConverter
This basic approach is very flexible and can be extended easily for many use cases.
This question already has answers here:
Validation Error: Value is not valid
(3 answers)
Closed 7 years ago.
I replaced rich:pickList to selectManyListbox for testing, when submit values to server it still displays error: "Validation Error: Value is not valid". I also set breakpoint to debug StaffConverter (getAsobject), but system never invokes it. Pls let me know some reasons why my converter never invoked and suggest me how to fix this. thanks
xhtml file:
<h:selectManyListbox value="#{reportController.selectedStaffList}" converter="staffConverter">
<f:selectItems value="#{reportController.staffList}"
var="item" itemValue="#{item}" itemLabel="#{item.name}" />
</h:selectManyListbox>
<rich:pickList value="#{reportController.selectedStaffList}" converter="staffConverter" sourceCaption="Available flight" targetCaption="Selected flight" listWidth="195px" listHeight="100px" orderable="true">
<f:selectItems value="#{reportController.staffList}" var="item" itemValue="#{item}" itemLabel="#{item.name}" />
</rich:pickList>
My Converter:
#FacesConverter(forClass = Staff.class)
public static class StaffConverter implements Converter {
public Object getAsObject(FacesContext facesContext, UIComponent component, String value) {
if (value == null || value.length() == 0) {
return null;
}
StaffController controller = StaffController.getInstance();
return controller.facade.find(Staff.class, getKey(value));
}
java.lang.Integer getKey(String value) {
java.lang.Integer key;
key = Integer.valueOf(value);
return key;
}
String getStringKey(java.lang.Integer value) {
StringBuffer sb = new StringBuffer();
sb.append(value);
return sb.toString();
}
public String getAsString(FacesContext facesContext, UIComponent component, Object object) {
if (object == null) {
return null;
}
if (object instanceof Staff) {
Staff o = (Staff) object;
return getStringKey(o.getStaffCode());
} else {
throw new IllegalArgumentException("object " + object + " is of type " + object.getClass().getName()
+ "; expected type: " + StaffController.class.getName());
}
}
}
I implemented equals method in Staff:
#Override
public boolean equals(Object object) {
if (!(object instanceof Staff)) {
return false;
}
Staff other = (Staff) object;
return (this.staffCode == other.staffCode);
}
Just to have it posted somewhere for people - like me - who like searching for solutions for development issues on google or stackoverflow...
I had this issue several times, depending on which kind of pojos I was using in the converters... Finally I think I found an elegant solution.
In my case I use JPA entity classes directly as I wanted to save the DTO layer. Well, with some entities the rich:pickList worked, with others not... I also tracked it down to the equals method. In the example below for instance it did not work for the userGroupConverter bean.
My solution is simply to inline overwrite the equals method, so the one from the entity (I often use lombok) is untouched and does not need to be changed, at all! So in my converter below I only compare the name field in equals:
xhtml:
<rich:pickList id="pickListUserGroupSelection"
value="#{usersBean.selectedUserGroups}" switchByDblClick="true"
sourceCaption="Available user groups" targetCaption="Groups assigned to user"
listWidth="365px" listHeight="100px" orderable="false"
converter="#{userGroupConverter}"
disabled="#{!rich:isUserInRole('USERS_MAINTAIN')}">
<f:validateRequired disabled="true" />
<rich:validator disabled="true" />
<f:selectItems value="#{usersBean.userGroups}" var="userGroup"
itemValue="#{userGroup}"
itemLabel="#{userGroup.name}" />
<f:selectItems value="#{usersBean.selectedUserGroups}" var="userGroup"
itemValue="#{userGroup}"
itemLabel="#{userGroup.name}" />
</rich:pickList>
<rich:message for="pickListUserGroupSelection" />
Converter:
package ...;
import ...
/**
* JSF UserGroup converter.<br>
* Description:<br>
* JSF UserGroup converter for rich:pickList elements.<br>
* <br>
* Copyright: Copyright (c) 2014<br>
*/
#Named
#Slf4j
#RequestScoped // must be request scoped, as it can change every time!
public class UserGroupConverter implements Converter, Serializable {
/**
*
*/
private static final long serialVersionUID = 9057357226886146751L;
#Getter
Map<String, UserGroup> groupMap;
#Inject
MessageUtil messageUtil;
#Inject
UserGroupDao userGroupDao;
#PostConstruct
public void postConstruct() {
groupMap = new HashMap<>();
List<UserGroup> userGroups;
try {
userGroups = userGroupDao.findAll(new String[] {UserGroup.FIELD_USER_ROLE_NAMES});
if(userGroups != null) {
for (UserGroup userGroup : userGroups) {
// 20150713: for some reason the UserGroup entity's equals method is not sufficient here and causes a JSF validation error
// "Validation Error: Value is not valid". I tried this overridden equals method and now it works fine :-)
#SuppressWarnings("serial")
UserGroup newGroup = new UserGroup() {
#Override
public boolean equals(Object obj){
if (!(obj instanceof UserGroup)){
return false;
}
return (getName() != null)
? getName().equals(((UserGroup) obj).getName())
: (obj == this);
}
};
newGroup.setName(userGroup.getName());
groupMap.put(newGroup.getName(), newGroup);
}
}
} catch (DaoException e) {
log.error(e.getMessage(), e);
FacesContext fc = FacesContext.getCurrentInstance();
FacesMessage message = new FacesMessage(FacesMessage.SEVERITY_INFO, "Error initializing user group converter!", null);
fc.addMessage(null, message);
}
}
/*
* (non-Javadoc)
* #see javax.faces.convert.Converter#getAsObject(javax.faces.context.FacesContext, javax.faces.component.UIComponent, java.lang.String)
*/
#Override
public UserGroup getAsObject(FacesContext context, UIComponent component,
String value) {
UserGroup ug = null;
try {
ug = getGroupMap().get(value);
} catch (Exception e) {
log.error(e.getMessage(), e);
FacesContext fc = FacesContext.getCurrentInstance();
FacesMessage message = new FacesMessage(FacesMessage.SEVERITY_INFO, "Error converting user group!", null);
fc.addMessage(null, message);
}
return ug;
}
/*
* (non-Javadoc)
* #see javax.faces.convert.Converter#getAsString(javax.faces.context.FacesContext, javax.faces.component.UIComponent, java.lang.Object)
*/
#Override
public String getAsString(FacesContext context, UIComponent component,
Object value) {
String name = ((UserGroup) value).getName();
return name;
}
}
Brgds and have fun!
Thanks Brian and BalusC for your help. I fixed my problem by adding named converter inside selectManyListbox & rich:picklist so they run well. But normally, I only use #FacesConverter(forClass = Staff.class), and don't need to add named converter in jsf so they still run well except for selectManyListbox & rich:picklist
Made a post earlier about this topic (Display values in drill-down SelectOneMenus). The problem that have is not connected to the Object converters as I first thought.
I have two selectOneMenu depending on each other, Sector -> Category. When I persist the background business object that holds a Sector and Category, everything works as expected. Even after the current user session terminates and the user log on again and edit the previous saved business object.
The problem occur when and the entity manager flushes the objects and user wants to display the Sector, Category again from the database. The Sector selectOneMenu displays its value as it should, but the Category selectOneMenu value is not displayed. Although the backing bean has the correct value from the database.
What makes the selectOneMenuto not display certain persisted values/objects when they are loaded from database instead from in-memory?
Edit.xhtml
<h:outputLabel value="Sector:" />
<h:selectOneMenu id="sectorSelector" value="#{activityController.selected.category.sector}" title="#{bundle.CreateSectorLabel_sectorName}" valueChangeListener="#{activityController.changeSectorMenu}" immediate="true">
<a4j:ajax event="change" execute="#this categoryMenu" render="categoryMenu"/>
<f:selectItems value="#{sectorController.itemsAvailableSelectOne}"/>
</h:selectOneMenu>
<h:outputLabel value="Category:" />
<h:selectOneMenu id="categoryMenu" value="#{activityController.selected.category}" title="#{bundle.CreateSectorLabel_sectorName}"
binding="#{activityController.categoryMenu}"
required="true" requiredMessage="#{bundle.CreateCategoryRequiredMessage_sector}">
<f:selectItems value="#{activityController.categorySelection}"/>
</h:selectOneMenu>
Controller bean for Category
#ManagedBean(name = "categoryController")
#SessionScoped
public class CategoryController implements Serializable{
....
#FacesConverter(forClass = Category.class)
public static class CategoryControllerConverter implements Converter {
#Override
public Object getAsObject(FacesContext facesContext, UIComponent component, String value) {
if (value == null || value.length() == 0) {
return null;
}
CategoryController controller = (CategoryController) facesContext.getApplication().getELResolver().
getValue(facesContext.getELContext(), null, "categoryController");
return controller.ejbFacade.find(getKey(value));
}
java.lang.Integer getKey(String value) {
java.lang.Integer key;
key = Integer.valueOf(value);
return key;
}
String getStringKey(java.lang.Integer value) {
StringBuffer sb = new StringBuffer();
sb.append(value);
return sb.toString();
}
#Override
public String getAsString(FacesContext facesContext, UIComponent component, Object object) {
if (object == null) {
return null;
}
if (object instanceof Category) {
Category o = (Category) object;
return getStringKey(o.getIdCategory());
}
else {
throw new IllegalArgumentException("object " + object + " is of type " + object.getClass().getName() + "; expected type: " + CategoryController.class.getName());
}
}
}
Part of POJO object
...
public class Category implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Basic(optional = false)
#Column(name = "idCategory")
private Integer idCategory;
...
#Override
public boolean equals(Object object) {
if (!(object instanceof Category)) {
return false;
}
Category other = (Category) object;
if ((this.idCategory == null && other.idCategory != null) || (this.idCategory != null && !this.idCategory.equals(other.idCategory))) {
return false;
}
return true;
}
Functon for creating the SelectItem[] array
public static SelectItem[] getSelectItems(List<?> entities, boolean selectOne) {
int size = entities.size();
SelectItem[] items;
int i = 0;
if (selectOne) {
items = new SelectItem[size + 1];
items[0] = new SelectItem("", ResourceBundle.getBundle("/resources/Bundle").getString("Select_item"));
i++;
} else {
items = new SelectItem[size];
}
for (Object x : entities) {
items[i++] = new SelectItem(x, x.toString());
}
return items;
}
The problem was that I didn't have the right collection of SelectItems when the user edited a business object. Plus that I added the <f:converter> to explicitly invoke the converter, where I did some changes in the getAsString() (the else part).
Thank you for your time, and hope the post can be useful to someone else.
SelectMenuOne
public String prepareEditOrganisationActivity() {
current = (Activity) organisationActivityItems.getRowData();
Sector sector = current.getCategory().getSector();
CategoryController categoryBean = JsfUtil.findBean("categoryController", CategoryController.class);
categorySelection = categoryBean.categoryItemsBySector(sector);
return "EditActivity";
}
CategoryControllerConverter
...
#Override
public String getAsString(FacesContext facesContext, UIComponent component, Object object) {
if (object == null) {
return null;
}
if (object instanceof Category) {
Category o = (Category) object;
return getStringKey(o.getIdCategory());
}
else {
Category tmp = new Category();
return getStringKey(tmp.getIdCategory());
}
}
When I am using h:selectOneRadio and supplying the list of values in a list as
the entire radio button section is exposed as a single unbroken list. I need to arrange it in 3 columns. I have tried giving
<h:panelGrid id="radioGrid" columns="3">
<h:selectOneRadio id="radio1" value="#{bean.var}">
<f:selectItems id="rval" value="#{bean.list}"/>
</h:selectOneRadio>
</h:panelGrid>
But there is no difference in the rendered section. Its not broken up into columns. What am I doing wrong?
I've adapted the code given by Damo, to work with h:selectOneRadio instead of h:selectManycheckbox. To get it working you will need to register it in your faces-config.xml, with:
<render-kit>
<renderer>
<component-family>javax.faces.SelectOne</component-family>
<renderer-type>javax.faces.Radio</renderer-type>
<renderer-class>test.components.SelectOneRadiobuttonListRenderer</renderer-class>
</renderer>
</render-kit>
To compile it, you will also need the JSF implementation (typically found in some sort of jsf-impl.jar in you app server).
The code outputs the radiobuttons in divs, instead of a table. You can then use CSS to style them however you would like. I would suggest giving a fixed width to the checkboxDiv and inner divs, and then having the inner divs display as inline blocks:
div.radioButtonDiv{
width: 300px;
}
div.radioButtonDiv div{
display: inline-block;
width: 100px;
}
Which should give the 3 columns you are looking for
The code:
package test.components;
import java.io.IOException;
import java.lang.reflect.Array;
import java.util.Collection;
import java.util.Iterator;
import javax.faces.component.NamingContainer;
import javax.faces.component.UIComponent;
import javax.faces.component.UISelectMany;
import javax.faces.component.UISelectOne;
import javax.faces.context.FacesContext;
import javax.faces.context.ResponseWriter;
import javax.faces.model.SelectItem;
import com.sun.faces.renderkit.RenderKitUtils;
import com.sun.faces.renderkit.html_basic.MenuRenderer;
import com.sun.faces.util.MessageUtils;
import com.sun.faces.util.Util;
/**
* This component ensures that h:selectOneRadio doesn't get rendered using
* tables. It is adapted from the code at:
* http://www.blog.locuslive.com/?p=15
*
* To register it for use, place the following in your faces config:
*
* <render-kit>
* <renderer>
* <component-family>javax.faces.SelectOne</component-family>
* <renderer-type>javax.faces.Radio</renderer-type>
* <renderer-class>test.components.SelectOneRadiobuttonListRenderer</renderer-class>
* </renderer>
* </render-kit>
*
* The original comment is below:
*
* ----------------------------------------------------------------------------- *
* This is a custom renderer for the h:selectManycheckbox
* It is intended to bypass the incredibly sucky table based layout used
* by the standard component.
*
* This layout uses an enclosing div with divs for each input.
* This gives a default layout similar to a vertical layout
* The layout can then be controlled by css
*
* This renderer assigns an class of "checkboxDiv" to the enclosing div
* The class and styleClass attributes are then applied to the internal
* divs that house the inputs
*
* The following attributes are ignored as they are no longer required when using CSS:
* - pageDirection
* - border
*
* Note that I am not supporting optionGroups at this stage. They would be relatively
* easy to implement with another enclosing div
*
* #author damianharvey
*
*/
public class SelectOneRadiobuttonListRenderer extends MenuRenderer {
public void encodeEnd(FacesContext context, UIComponent component)
throws IOException {
if (context == null) {
throw new NullPointerException(
MessageUtils.getExceptionMessageString(MessageUtils.NULL_PARAMETERS_ERROR_MESSAGE_ID,
"context"));
}
if (component == null) {
throw new NullPointerException(
MessageUtils.getExceptionMessageString(MessageUtils.NULL_PARAMETERS_ERROR_MESSAGE_ID,
"component"));
}
// suppress rendering if "rendered" property on the component is
// false.
if (!component.isRendered()) {
return;
}
ResponseWriter writer = context.getResponseWriter();
assert(writer != null);
writer.startElement("div", component);
if (shouldWriteIdAttribute(component)) {
writeIdAttributeIfNecessary(context, writer, component);
}
writer.writeAttribute("class", "radioButtonDiv", "class");
Iterator items = RenderKitUtils.getSelectItems(context, component).iterator();
SelectItem curItem = null;
int idx = -1;
while (items.hasNext()) {
curItem = (SelectItem) items.next();
idx++;
renderOption(context, component, curItem, idx);
}
writer.endElement("div");
}
protected void renderOption(FacesContext context, UIComponent component, SelectItem curItem, int itemNumber)
throws IOException {
ResponseWriter writer = context.getResponseWriter();
assert(writer != null);
// disable the check box if the attribute is set.
String labelClass = null;
boolean componentDisabled = Util.componentIsDisabled(component);
if (componentDisabled || curItem.isDisabled()) {
labelClass = (String) component.
getAttributes().get("disabledClass");
} else {
labelClass = (String) component.
getAttributes().get("enabledClass");
}
writer.startElement("div", component); //Added by DAMIAN
String styleClass = (String) component.getAttributes().get("styleClass");
String style = (String) component.getAttributes().get("style");
if (styleClass != null) {
writer.writeAttribute("class", styleClass, "class");
}
if (style != null) {
writer.writeAttribute("style", style, "style");
}
writer.startElement("input", component);
writer.writeAttribute("name", component.getClientId(context), "clientId");
String idString = component.getClientId(context) + NamingContainer.SEPARATOR_CHAR + Integer.toString(itemNumber);
writer.writeAttribute("id", idString, "id");
String valueString = getFormattedValue(context, component, curItem.getValue());
writer.writeAttribute("value", valueString, "value");
writer.writeAttribute("type", "radio", null);
Object submittedValues[] = getSubmittedSelectedValues(context, component);
boolean isSelected;
Class type = String.class;
Object valuesArray = null;
Object itemValue = null;
if (submittedValues != null) {
valuesArray = submittedValues;
itemValue = valueString;
} else {
valuesArray = getCurrentSelectedValues(context, component);
itemValue = curItem.getValue();
}
if (valuesArray != null) {
type = valuesArray.getClass().getComponentType();
}
// I don't know what this does, but it doens't compile. Commenting it
// out doesn't seem to hurt
// Map<String, Object> requestMap = context.getExternalContext().getRequestMap();
// requestMap.put(ConverterPropertyEditorBase.TARGET_COMPONENT_ATTRIBUTE_NAME,
// component);
Object newValue = context.getApplication().getExpressionFactory().
coerceToType(itemValue, type);
isSelected = isSelected(newValue, valuesArray);
if (isSelected) {
writer.writeAttribute(getSelectedTextString(), Boolean.TRUE, null);
}
// Don't render the disabled attribute twice if the 'parent'
// component is already marked disabled.
if (!Util.componentIsDisabled(component)) {
if (curItem.isDisabled()) {
writer.writeAttribute("disabled", true, "disabled");
}
}
// Apply HTML 4.x attributes specified on UISelectMany component to all
// items in the list except styleClass and style which are rendered as
// attributes of outer most table.
RenderKitUtils.renderPassThruAttributes(writer, component, new String[] { "border", "style" });
RenderKitUtils.renderXHTMLStyleBooleanAttributes(writer, component);
writer.endElement("input");
writer.startElement("label", component);
writer.writeAttribute("for", idString, "for");
// if enabledClass or disabledClass attributes are specified, apply
// it on the label.
if (labelClass != null) {
writer.writeAttribute("class", labelClass, "labelClass");
}
String itemLabel = curItem.getLabel();
if (itemLabel != null) {
writer.writeText(" ", component, null);
if (!curItem.isEscape()) {
// It seems the ResponseWriter API should
// have a writeText() with a boolean property
// to determine if it content written should
// be escaped or not.
writer.write(itemLabel);
}
else {
writer.writeText(itemLabel, component, "label");
}
}
writer.endElement("label");
writer.endElement("div"); //Added by Damian
}
// ------------------------------------------------- Package Private Methods
String getSelectedTextString() {
return "checked";
}
/** For some odd reason this is a private method in the MenuRenderer superclass
*
* #param context
* #param component
* #return
*/
private Object getCurrentSelectedValues(FacesContext context,
UIComponent component) {
if (component instanceof UISelectMany) {
UISelectMany select = (UISelectMany) component;
Object value = select.getValue();
if (value instanceof Collection) {
Collection<?> list = (Collection) value;
int size = list.size();
if (size > 0) {
// get the type of the first element - Should
// we assume that all elements of the List are
// the same type?
return list.toArray((Object[]) Array.newInstance(list.iterator().next().getClass(), size));
}
else {
return ((Collection) value).toArray();
}
}
else if (value != null && !value.getClass().isArray()) {
logger.warning("The UISelectMany value should be an array or a collection type, the actual type is " + value.getClass().getName());
}
return value;
}
UISelectOne select = (UISelectOne) component;
Object returnObject;
if (null != (returnObject = select.getValue())) {
Object ret = Array.newInstance(returnObject.getClass(), 1);
Array.set(ret, 0, returnObject);
return ret;
}
return null;
}
/** For some odd reason this is a private method in the MenuRenderer superclass
*
* #param context
* #param component
* #return
*/
private Object[] getSubmittedSelectedValues(FacesContext context, UIComponent component) {
if (component instanceof UISelectMany) {
UISelectMany select = (UISelectMany) component;
return (Object[]) select.getSubmittedValue();
}
UISelectOne select = (UISelectOne) component;
Object returnObject;
if (null != (returnObject = select.getSubmittedValue())) {
return new Object[] { returnObject };
}
return null;
}
/** For some odd reason this is a private method in the MenuRenderer superclass
*
* #param itemValue
* #param valueArray
* #return
*/
private boolean isSelected(Object itemValue, Object valueArray) {
if (null != valueArray) {
if (!valueArray.getClass().isArray()) {
logger.warning("valueArray is not an array, the actual type is " + valueArray.getClass());
return valueArray.equals(itemValue);
}
int len = Array.getLength(valueArray);
for (int i = 0; i < len; i++) {
Object value = Array.get(valueArray, i);
if (value == null) {
if (itemValue == null) {
return true;
}
}
else if (value.equals(itemValue)) {
return true;
}
}
}
return false;
}
}
The h:panelGrid contains only one child (a h:selectOneRadio), so it will only ever render one column. The h:selectOneRadio renders a HTML table too. Its renderer only offers two layouts (lineDirection and pageDirection).
You have a few options
use JavaScript to modify the table after page load
find a 3rd party control that implements the functionality you want
write your own selectOneRadio control
Tomahawk does the magic! Check it out!
http://wiki.apache.org/myfaces/Display_Radio_Buttons_In_Columns