Dynamic JSF converter - jsf

The data access layer behind my JSF app uses two different formats for date fields (sometimes ddmmyyyy, sometimes yyyymmdd); the displayed date is always dd/mm/yyyy.
Is there a way to use two different converters for a single field and decide which one to use dynamically? Like "if this command button is clicked, use this converter, else if this other command button is clicked, use that converter".

BalusC explains why this is difficult here: https://stackoverflow.com/a/7123153/3284943
Based on his suggestions, I created this:
in the JSF file, I use my custom converter and its defined attributes, to select the converter and dynamic attributes at runtime. This is a snippet from a dynamic PrimeFaces DataTable:
<h:outputText rendered = "#{column.dateTime}" value="#{table[column.name]}">
<f:converter converterId="runtimeConverterSelector" />
<f:attribute name="converterId" value="#{column.converterName}" />
<f:attribute name="pattern" value="#{column.converterPattern}" />
</h:outputText>
The custom converter:
#FacesConverter ("runtimeConverterSelector")
public class RuntimeConverterSelector implements Converter {
Converter delegateConverter;
#Override
public Object getAsObject(FacesContext context, UIComponent component, String value) {
retrieveAttributes(component);
return delegateConverter.getAsObject(context, component, value);
}
#Override
public String getAsString(FacesContext context, UIComponent component, Object value) {
retrieveAttributes(component);
return delegateConverter.getAsString(context, component, value);
}
private void retrieveAttributes(UIComponent component) {
Object attribute;
attribute = component.getAttributes().get("converterId");
if (attribute != null) {
String converterName = (String)attribute;
switch (converterName) {
case "javax.faces.BigDecimal":
delegateConverter = new BigDecimalConverter();
break;
case "javax.faces.BigInteger":
delegateConverter = new BigIntegerConverter();
break;
case "javax.faces.Boolean":
delegateConverter = new BooleanConverter();
break;
case "javax.faces.Byte":
delegateConverter = new ByteConverter();
break;
case "javax.faces.Character":
delegateConverter = new CharacterConverter();
break;
case "javax.faces.DateTimeConverter":
delegateConverter = new DateTimeConverter();
attribute = component.getAttributes().get("pattern");
if (attribute != null) ((DateTimeConverter)delegateConverter).setPattern((String)attribute);
attribute = component.getAttributes().get("timeZone");
if (attribute != null) ((DateTimeConverter)delegateConverter).setTimeZone(TimeZone.getTimeZone((String)attribute));
attribute = component.getAttributes().get("dateStyle");
if (attribute != null) ((DateTimeConverter)delegateConverter).setDateStyle((String)attribute);
attribute = component.getAttributes().get("timeStyle");
if (attribute != null) ((DateTimeConverter)delegateConverter).setDateStyle((String)attribute);
attribute = component.getAttributes().get("type");
if (attribute != null) ((DateTimeConverter)delegateConverter).setDateStyle((String)attribute);
break;
case "javax.faces.Double":
delegateConverter = new DoubleConverter();
break;
case "javax.faces.Enum":
delegateConverter = new EnumConverter();
break;
case "javax.faces.Float":
delegateConverter = new FloatConverter();
break;
case "javax.faces.Integer":
delegateConverter = new IntegerConverter();
break;
case "javax.faces.Long":
delegateConverter = new LongConverter();
break;
case "javax.faces.Number":
delegateConverter = new NumberConverter();
attribute = component.getAttributes().get("currencyCode");
if (attribute != null) ((NumberConverter)delegateConverter).setCurrencyCode((String)attribute);
attribute = component.getAttributes().get("currencySymbol");
if (attribute != null) ((NumberConverter)delegateConverter).setCurrencySymbol((String)attribute);
attribute = component.getAttributes().get("groupingUsed");
if (attribute != null) ((NumberConverter)delegateConverter).setGroupingUsed(Boolean.parseBoolean((String)attribute));
attribute = component.getAttributes().get("integerOnly");
if (attribute != null) ((NumberConverter)delegateConverter).setIntegerOnly(Boolean.parseBoolean((String)attribute));
attribute = component.getAttributes().get("locale");
if (attribute != null) ((NumberConverter)delegateConverter).setLocale(new Locale((String)attribute));
attribute = component.getAttributes().get("maxFractionDigits");
if (attribute != null) ((NumberConverter)delegateConverter).setMaxFractionDigits(Integer.parseInt((String)attribute));
attribute = component.getAttributes().get("maxIntegerDigits");
if (attribute != null) ((NumberConverter)delegateConverter).setMaxIntegerDigits(Integer.parseInt((String)attribute));
attribute = component.getAttributes().get("minFractionDigits");
if (attribute != null) ((NumberConverter)delegateConverter).setMinFractionDigits(Integer.parseInt((String)attribute));
attribute = component.getAttributes().get("minIntegerDigits");
if (attribute != null) ((NumberConverter)delegateConverter).setMinIntegerDigits(Integer.parseInt((String)attribute));
attribute = component.getAttributes().get("pattern");
if (attribute != null) ((NumberConverter)delegateConverter).setPattern((String)attribute);
attribute = component.getAttributes().get("type");
if (attribute != null) ((NumberConverter)delegateConverter).setType((String)attribute);
break;
case "javax.faces.Short":
delegateConverter = new ShortConverter();
break;
default:
System.err.println("ConverterId provided to runtimeConverterSelector, '" + converterName + "' is invalid. The default converter will be used");
break;
}
} else {
System.err.println("No converterId was provided to runtimeConverterSelector. The default converter will be used.");
}
}
}

AFAIK it's not possible. But you can check for both formats within one converter. Of course, conversion to string will be done basing to one format. Alternatively, you can format date basing on the current Locale. Basic example:
#FacesConverter("doubleDateConverter")
public class DateConverter implements Converter {
public Object getAsObject(FacesContext context, UIComponent component, String value) {
if(value == null || value.equals("")) {
return null;
}
SimpleDateFormat format1 = new SimpleDateFormat("ddMMyyyy");
SimpleDateFormat format2 = new SimpleDateFormat("yyyyMMdd");
Date date = null;
boolean valid = true;
try {
date = format1.parse(value);
} catch (ParseException e) {
valid = false;
}
if(!valid) {
try {
date = format2.parse(value);
} catch (ParseException e) {
valid = false;
}
}
if((!valid) || (date == null)) {
throw new ConverterException(new FacesMessage("Date is in wrong format: " + value));
}
return date;
}
public String getAsString(FacesContext context, UIComponent component, Object value) {
if (!(value instanceof Date) || (value == null)) {
return null;
}
SimpleDateFormat format = new SimpleDateFormat("ddMMyyyy");
return format.format((Date)value);
}
}

Related

Primefaces input mask for date converter

Well. I find in all places and I still without solution. I need to do a input mask with Primefaces and bind the value to a Date object in my bean.
The problem is: I use a converter with all validations, convertions and formats with my own code. My answer is: Is other solution with better performance. I hope so. Please help.
P/D: I don't need use a pickdate. I need the input mask to do this
I use this:
<div style="margin-bottom:1em;font-size: 1.2em;">
<p:inputMask id="dob" mask="99/99/9999" value="#{viewMB.request.dateOfBirth}" style="width:8em;" >
<f:convertDateTime pattern="MM/dd/yyyy" />
</p:inputMask>
<p:watermark value="MM/DD/YYYY" for="dob" />
</div>
and you can still add custom validator if you wish.
Well, this is my solution
<h:form>
<p:inputMask mask="99-99-9999" value="#{mask.date}" converterMessage="Invalid Date!" converter="dateconverter" />
<h:commandButton actionListener="#{mask.submit()}" value="Submit" />
</h:form>
And the converter
#Override
public Object getAsObject(FacesContext context, UIComponent component, String value) {
System.out.println(value);
if (value != null) {
try {
if (validateDate(value)) {
return convertDate(value).toDate();
}
else{
throw new ConverterException("Invalid date!");
}
} catch (Exception e) {
throw new ConverterException("Invalid date!");
}
}
throw new ConverterException("Null String!");
}
#Override
public String getAsString(FacesContext context, UIComponent component, Object value) {
System.out.println(value);
if (value != null) {
try {
Date now = (Date)value;
DateTime d = new DateTime(now);
return d.getDayOfMonth() + "-" + d.monthOfYear() + "-" + d.getYear();
} catch (Exception e) {
throw new ConverterException("Convertion failure!");
}
}
throw new ConverterException("Null object!");
}
private boolean validateDate(String param) throws ParseException{
//Param is a date format from input mask
String[] values = param.split("-");
int day = Integer.valueOf(values[0]);
int month = Integer.valueOf(values[1]);
int year = Integer.valueOf(values[2]);
DateTime converted = convertDate(param);
if (converted.getDayOfMonth() != day || converted.getMonthOfYear() != month || converted.getYear() != year) {
return false;
}
else{
return true;
}
}
private DateTime convertDate(String param) throws ParseException{
SimpleDateFormat sdf = new SimpleDateFormat("dd-MM-yyyy");
Date convertedCurrentDate = sdf.parse(param);
return new DateTime(convertedCurrentDate);
}
The Managed Bean
#ManagedBean(name="mask")
public class MaskBean {
private Date date;
public Date getDate() {
return date;
}
public void setDate(Date date) {
this.date = date;
}
public void submit(){
RequestContext context = RequestContext.getCurrentInstance();
context.execute("alert('date submited: value recibed " + date.toString() + "')");
}
}
It work for me.

Dynamically preselect items in HtmlSelectOneMenu with OmniFaces SelectItemsConverter

I want to dynamically create a dropdown (HtmlSelectOneMenu) with all possible options. The option currently set should be preselected. To achieve this I created a value expression for my form:
String jsfValue = String.format("#{%s.item.%s}", getControllerBeanName(), key);
ValueExpression valueExpression = JSFUtils.createValueExpression(jsfValue, String.class);
menu.setValueExpression("value", valueExpression);
The string #{%s.item.%s} evaluates to #{playlistController.item.category} and category is the object of type Category that I want to bind to my dropdown.
To use the SelectItemsConverter from OmniFaces I changed the toString() method of Category to:
#Override
public String toString() {
return String.format("%s[id=%d]", getClass().getSimpleName(), getId());
}
My code for the form generation looks like this (Note: Category extends BaseEntity):
private UIComponent createDropdown(FormInput property) {
String key = property.getKey();
String beanName = key + "Controller";
GenFormBaseController controller = (GenFormBaseController) JSFUtils.getManagedBean(beanName);
List<BaseEntity> list = controller.getService().findAll();
List<SelectItem> selectItems = new ArrayList<>(list.size());
for (BaseEntity itemInList : list) {
selectItems.add(new SelectItem(itemInList, itemInList.getName()));
}
UISelectItems items = new UISelectItems();
items.setValue(selectItems.toArray());
HtmlSelectOneMenu menu = new HtmlSelectOneMenu();
menu.setConverter(new SelectItemsConverter());
menu.setId(key);
menu.getChildren().add(items);
String jsfValue = String.format("#{%s.item.%s}", getControllerBeanName(), key);
ValueExpression valueExpression = JSFUtils.createValueExpression(jsfValue, String.class);
menu.setValueExpression("value", valueExpression);
return menu;
}
If I set menu.setConverter(new SelectItemsConverter()); then the wrong item is preselected. But If I remove it, then the correct item is selected but when I try to save the form it fails because it has no converter for the dropdown.
Can anyone help me? I've published the code on GitHub.
The method createDropdown can be found at the bottom of the linked code.
Your mistake is here, in the String.class type argument:
ValueExpression valueExpression = JSFUtils.createValueExpression(jsfValue, String.class);
A Category is not a String. Use Category.class instead and everything should be well. At least, theoretically. I can't copy'n'paste'n'run this piece of code without changes.
I found the error in the code. Simply the equals() and hashCode() of the BaseEntity were insufficient. And the error BalusC already mentioned. The resulting code looks like the following:
GenFormBaseController.java
private UIComponent createDropdown(FormInput property) {
String key = property.getKey();
Class<?> expectedType = property.getValue();
String beanName = lowerFirstChar(expectedType.getSimpleName()) + "Controller";
GenFormBaseController controller = (GenFormBaseController) JSFUtils.getManagedBean(beanName);
List<BaseEntity> list = controller.getService().findAll();
List<SelectItem> selectItems = new ArrayList<>(list.size());
for (BaseEntity itemInList : list) {
selectItems.add(new SelectItem(itemInList, itemInList.getName()));
}
UISelectItems items = new UISelectItems();
items.setValue(selectItems);
HtmlSelectOneMenu menu = new HtmlSelectOneMenu();
menu.setConverter(new SelectItemsConverter());
menu.setId(key);
menu.getChildren().add(items);
String jsfValue = String.format("#{%s.item.%s}", getControllerBeanName(), key);
ValueExpression valueExpression = JSFUtils.createValueExpression(jsfValue, expectedType);
menu.setValueExpression("value", valueExpression);
return menu;
}
BaseEntity.java
#Override
public int hashCode() {
int hash = 7;
hash = 97 * hash + Objects.hashCode(this.id);
return hash;
}
#Override
public boolean equals(Object obj) {
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
final BaseEntity other = (BaseEntity) obj;
if (!Objects.equals(this.id, other.id)) {
return false;
}
return true;
}

JSF 2 - Required Checkbox

I'm trying to write a required checkbox that for terms and conditions that a user must accept. I dont actually need any backing bean I just need to make sure the user checks the box.
I've tried the following but it doesn't seem to work:
JSF:
<p:selectBooleanCheckbox required="true"> <f:validator validatorId="RequiredCheckboxValidator" /></p:selectBooleanCheckbox>
Java code:
#FacesValidator("RequiredCheckboxValidator")
public class RequiredCheckboxValidator implements Validator {
public void validate(FacesContext context, UIComponent component, Object value)
throws ValidatorException
{
if (value.equals(Boolean.FALSE)) {
String requiredMessage = ((UIInput) component).getRequiredMessage();
if (requiredMessage == null) {
Object label = component.getAttributes().get("label");
if (label == null || (label instanceof String && ((String) label).length() == 0)) {
label = component.getValueExpression("label");
}
if (label == null) {
label = component.getClientId(context);
}
requiredMessage = MessageFormat.format(UIInput.REQUIRED_MESSAGE_ID, label);
}
throw new ValidatorException(
new FacesMessage(FacesMessage.SEVERITY_ERROR, requiredMessage, requiredMessage));
}
}
}

selectOneMenu do not render/display value

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());
}
}

How to set default value to <h:selectOneMenu>

I am trying to set default value to h:selectOneMenu. But, It is not working.
This is my code
index.xhtml
<h:body>
<h:form id="test">
<h:selectOneMenu value="#{selectMenuBean.selectedItem}"
title="select version"
onchange="submit()"
disabled="false" id="combo">
<f:selectItems value="#{selectMenuBean.selectItems}" />
</h:selectOneMenu>
</h:form>
</h:body>
BackingBean
private String selectedItem;
private List selectItems;
private int version=3;
public List getSelectItems() {
List<Version> selectedItems = ExportDao.getVersionsList();
System.out.println("List size: "+selectedItems.size());
selectItems = new ArrayList();
for (Version v1 : selectedItems) {
String DATE_FORMAT = "yyyy-MM-dd HH:mm";
//Create object of SimpleDateFormat and pass the desired date format.
SimpleDateFormat sdf = new SimpleDateFormat(DATE_FORMAT);
selectItems.add(new SelectItem(v1.getVersion(), "V" + v1.getVersion() + "/" + sdf.format(v1.getDate())));
if(version = v1.getVersion()) // I have to check the version and set the matching version as selected.
selectedItem = "V" + v1.getVersion() + "/" + sdf.format(v1.getDate());
}
return selectItems;
}
You're setting the selectedItem with the item label instead of the item value.
Replace
selectedItem = "V" + v1.getVersion() + "/" + sdf.format(v1.getDate());
by
selectedItem = v1.getVersion();
A few possible solutions:
1) Set the type of selectItems to SelectItem[] instead of and untyped List.
or 2) Try setting the var, itemValue and itemLabel attributes of the selectItems like below, and put actual Version objects in your list.
or my favorite, 3) Make a VersionConverter that knows how to convert a Version object from and to a String. Example below if your Version object is persisted in a database and has an Id. After this is constructed, your selectedItem and List selectItems should have the typ Version (and List), not String. JSF will handle the conversion by itself.
#FacesConverter(forClass=Version.class)
public class VersionConverter implements Converter{
public VersionConverter() {
}
#Override
public Object getAsObject(FacesContext facesContext, UIComponent component, String value) {
if (value == null || value.length() == 0) {
return null;
}
try {
// Get an EJB that can fetch the Version from a DB. Alternativly, do whatever you need to get your object from a string.
InitialContext ic = new InitialContext();
MyDao myDao = (MyDao)ic.lookup(String.format("java:global/%s/MyBean", (String)ic.lookup("java:module/ModuleName")));
return myDao.findEntity(Version.class, getKey(value));
} catch (NamingException e) {
return null;
}
}
Long getKey(String value) {
Long key;
key = Long.valueOf(value);
return key;
}
String getStringKey(Long value) {
StringBuilder sb = new StringBuilder();
sb.append(value);
return sb.toString();
}
#Override
public String getAsString(FacesContext facesContext, UIComponent component, Object object) {
if (object == null) {
return null;
}
if (object instanceof Version) {
Version e = (Version) object;
return getStringKey(e.getId());
}
else
throw new IllegalArgumentException("object " + object + " is of type " + object.getClass().getName() + "; expected type: " + Version.class.getName());
}
}

Resources