Simple customization bean for dates in dynamic view panel - xpages

I need to write a simple customization bean for a dynamic view panel so dates will always be displayed as yyyy-MM-dd but I have no clue which method to overwrite and how to modify my value so it shows what I want.
Any starter code would be apprciated (and yes, I looked at Jesse's code and it is way too complex for what I want to achieve).
Thanks
Edit: This now the code I have in my customization bean, but it does absolutely nothing...
public class DynamicViewCustomizerBean_Ben extends DominoViewCustomizer {
public static class ExtendedViewColumnConverter extends ViewColumnConverter {
#Override
public String getValueAsString(final FacesContext context, final UIComponent component, final Object value) {
if(value instanceof DateTime) {
SimpleDateFormat fmt = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
return fmt.format(value);
}
if(value instanceof Date) {
SimpleDateFormat fmt = new SimpleDateFormat("yyyy-MM-dd");
return fmt.format(value);
}
// for other cases, just return super
return super.getValueAsString(context, component, value);
}
}
}
And yes, the name of my customization bean is set properly on my Dynamic view panel:
<xe:dynamicViewPanel id="dynamicViewPanel1"
showColumnHeader="true"
customizerBean="com.videotron.xpages.DynamicViewCustomizerBean_Ben"
var="rowData">
...
Am I missing something? Is it the good event that is being overridden? I'm asking because if I set a value of "test" instead of the fmt.format(), it doesn't even show up. Nothing in the logs, no visible errors... I can't seem to find a working example of this on the web...

In the ExtendedViewColumnConverter.getValueAsString(FacesContext, UIComponent, Object) method of your customizer bean you need to return the desired value if the value object is a Date instance.
Here's a simple example:
if (value instanceof Date) {
DateFormat fmt = new SimpleDateFormat("yyyy-MM-dd");
return fmt.format(value);
}

I tend to use a request scoped bean that holds a few useful methods I found myself often to need.
The java class:
public class DateBean implements Serializable {
private static final long serialVersionUID = 1L;
private Locale locale;
private Date now;
private String shortDatePattern;
public void setLocale(Locale locale) {
this.locale = locale;
}
public Date getNow() {
if (now == null) {
now = new Date();
}
return now;
}
public String getShortDatePattern() {
if (shortDatePattern == null) {
SimpleDateFormat sdf = (SimpleDateFormat) SimpleDateFormat.getDateInstance(
SimpleDateFormat.SHORT, locale);
shortDatePattern = sdf.toLocalizedPattern()
.replaceAll("y+", "yyyy")
.replaceAll("M+","MM")
.replaceAll("d+", "dd");
}
return shortDatePattern;
}
...
}
Of course, this is just an example, you can tweak to your like
In the faces-config.xml
<managed-bean>
<managed-bean-name>date</managed-bean-name>
<managed-bean-class>demo.DateBean
</managed-bean-class>
<managed-bean-scope>request</managed-bean-scope>
<managed-property>
<property-name>locale</property-name>
<value>#{facesContext.viewRoot.locale}</value>
</managed-property>
</managed-bean>
Then, on the XPage:
<xp:text value="#{record.myDate}">
<xp:this.converter>
<xp:convertDateTime type="date"
pattern="${date.shortDatePattern}" />
</xp:this.converter>
</xp:text>

Related

ValueChangeEvent although value has not changed

I cannot see why the question is duplicate. If I debug the code then - when the button is clicked - no new value of projectSelected is being detected. Even the hashCode is the same. The equals method of the ProjectEntity only contains the id which is the same since it comes from the database and is not changed anywhere. Null values don't exist in the selection.
There was, however, too much code to reproduce the problem. I removed unnecessary code and the problem still persists.
Original question: In the following form with 3 <p:selectOneMenu> -fields if the submit button is clicked a valueChangeEvent is fired for the projectSelector field although it hasn't changed. Why is that? Like that the actual action behind the button is never called. I would expect a valueChangeEvent to be fired only in case the project changes.
Update: Trying to find the cause I replaced the ProjectEntity with String and then it worked. So I thought it must be the equals method of ProjectEntity but that only compares the id. I debugged further and found out that the selected value is being compared with a ProjectEntity with all fields set to null which gives a false and hence a valueChangeEvent. So the question is why is there a ProjectEntity with all fields set to null? I debugged into UIInput.compareValues which has that "null"-ProjectEntity being the previous value. That is being returned by UIOuput.getLocalValue. Where does it come from?
Update2: Even when using the equals and hashCode from selectOneMenu shows after submit always the last item in the list as selected item the behaviour does not change. I created an ear file readily to be deployed to e.g. a wildfly and would appreciate any help since I am stuck on this question.
<h:form>
<p:outputLabel value="#{msgs.timeProject}"/>
<p:selectOneMenu value="#{timeBean.model.projectSelected}"
converter="projectConverter"
onchange="submit()"
valueChangeListener="#{timeBean.projectChanged}"
immediate="true"
required="true">
<f:selectItems value="#{timeBean.model.allProjects}"
var="singleProject"
itemValue="#{singleProject}"
itemLabel="#{singleProject.name}"/>
</p:selectOneMenu>
<p:commandButton value="#{msgs.send}"
action="#{timeBean.myAction}"
ajax="false"/>
<p:outputLabel value="#{timeBean.model.resultValue}"
rendered="#{not empty timeBean.model.resultValue}"/>
</h:form>
The converter
#FacesConverter(value = "projectConverter")
public class ProjectConverter implements Converter {
#Inject
private ProjectService projectService;
#Override
public Object getAsObject(final FacesContext facesContext, final UIComponent uiComponent, final String projectName) {
if (StringUtils.isEmpty(projectName)) {
return null;
}
final List<ProjectEntity> projects = projectService.findAll();
for (ProjectEntity project : projects) {
if (StringUtils.equals(projectName, project.getName())) {
return project;
}
}
return null;
}
#Override
public String getAsString(final FacesContext facesContext, final UIComponent uiComponent, final Object value) {
if (value == null) {
return null;
}
if (value instanceof ProjectEntity) {
return ((ProjectEntity) value).getName();
}
return "???projectName???";
}
}
The equals-method of the ProjectEntity
#Override
public boolean equals(final Object o) {
if (this == o)
return true;
if (o == null || getClass() != o.getClass())
return false;
final ProjectEntity that = (ProjectEntity) o;
return id != null ? id.equals(that.id) : that.id == null;
}
And the change listener inside the timeBean
public void projectChanged(final ValueChangeEvent event) {
final ProjectEntity projectSelected = (ProjectEntity) event.getNewValue();
model.setProjectSelected(projectSelected);
final FacesContext context = FacesContext.getCurrentInstance();
context.renderResponse();
}
The TimeModel
public class TimeModel {
private ProjectEntity projectSelected;
private List<ProjectEntity> allProjects;
private String resultValue;
... getters and setters ...
I'll guess, that the problem resides inside the ProjectConverter class, cause it may run into troubles to assign a valid projectService instance. Maybe you remove the injection and try to compute the value programatically in the getAsObject, getAsString methods by explicit cdi-finders.
I remember to run in a similar situation, when i was injecting in a ServletFilter.

java.math.BigDecimal in p:selectOneMenu

Converter :
#FacesConverter("bigDecimalConverter")
public class BigDecimalConverter implements Converter {
private static final int SCALE = 2;
#Override
public Object getAsObject(FacesContext context, UIComponent component, String value) {
if (value == null || value.isEmpty()) {
return null;
}
try {
return new BigDecimal(value);
} catch (NumberFormatException e) {
throw new ConverterException(new FacesMessage(FacesMessage.SEVERITY_ERROR, null, "Message"), e);
}
}
#Override
public String getAsString(FacesContext context, UIComponent component, Object value) {
if (value == null) {
return "";
}
BigDecimal newValue;
if (value instanceof Long) {
newValue = BigDecimal.valueOf((Long) value);
} else if (value instanceof Double) {
newValue = BigDecimal.valueOf((Double) value);
} else if (!(value instanceof BigDecimal)) {
throw new ConverterException("Message");
} else {
newValue = (BigDecimal) value;
}
DecimalFormat formatter = (DecimalFormat) NumberFormat.getNumberInstance();
formatter.setGroupingUsed(false);
formatter.setMinimumFractionDigits(SCALE);
formatter.setMaximumFractionDigits(SCALE);
return formatter.format(newValue);
}
}
List :
<p:selectOneMenu id="list" value="#{bean.value}">
<f:selectItems var="row" value="#{bean.list}" itemLabel="#{row}" itemValue="#{row}"/>
<f:converter converterId="bigDecimalConverter"/>
</p:selectOneMenu>
<p:message id="msg" for="list"/>
<p:commandButton value="Submit" update="list msg" actionListener="#{bean.action}"/>
The managed bean backed by the above <p:selectOneMenu> :
#ManagedBean
#ViewScoped
public class Bean implements Serializable {
private List<BigDecimal> list; // Getter only.
private BigDecimal value; // Getter & setter.
private static final long serialVersionUID = 1L;
public Bean() {}
#PostConstruct
private void init() {
list = new ArrayList<BigDecimal>(){{
add(BigDecimal.valueOf(10));
add(BigDecimal.valueOf(20.11));
add(BigDecimal.valueOf(30));
add(BigDecimal.valueOf(40));
add(BigDecimal.valueOf(50));
}};
}
public void action() {
System.out.println("action() called : " + value);
}
}
A validation message, "Validation Error: Value is not valid" appears upon submission of the form. The getAsObject() method throws no exception upon form submission.
If a value with a scale like 20.11 is selected in the list, then the validation passes. It appears that the equals() method in the java.math.BigDecimal class goes fishy which for example, considers two BigDecimal objects equal, only if both of them are equal in value and scale thus 10.0 != 10.00 which requires compareTo() for them to be equal.
Any suggestion?
You lose information when converting to String. The default JSF BigDecimalConverter does this right, it uses BigDecimal#toString in its getAsString. The BigDecimal#toString's javadoc says:
There is a one-to-one mapping between the distinguishable BigDecimal values and the result of this conversion. That is, every distinguishable BigDecimal value (unscaled value and scale) has a unique string representation as a result of using toString. If that string representation is converted back to a BigDecimal using the BigDecimal(String) constructor, then the original value will be recovered.
This is exactly what you need. Don't treat converters like they must produce user readable-writable results when converting to String. They mustn't and often don't. They produce a String representation of an object, or an object reference. That's it. The selectItems' itemLabel defines a user readable representation in this case. I'm assuming that you don't want user writable values here, that you really have a fixed list of values for user to choose from.
If you really mean that this data must always have a scale of 2, and you need a user writable value, then that would be better checked in a validator, and user input could be helped out with p:inputMask.
Finally, let's set aside the fact, that your converter is not the best. It says "data must have a scale of 2". Then your should provide conforming data in your selectItems. More generally, server defined values must conform to relevant converters and validators. E.g. you could have problems in the same vein, when using the DateTimeConverter with the pattern "dd.MM.yyyy", but setting the default value to be new Date() without getting rid of the time part.
See also (more general notions about converters): https://stackoverflow.com/a/30529976/1341535

Overriding the JSF converter javax.faces.convert.BigDecimalConverter in favor of a custom converter using #FacesConverter(forClass = BigDecimal.class)

I have the following general BigDecimal converter (deeply reviewing the code is absolutely superfluous).
#FacesConverter(value = "bigDecimalConverter")
public class BigDecimalConverter implements Converter {
#Inject
private CurrencyRateBean currencyRateBean; // Maintains a currency selected by a user in his/her session.
private static final int scale = 2; // Taken from an enum.
#Override
public Object getAsObject(FacesContext context, UIComponent component, String value) {
if (!StringUtils.isNotBlank(value)) {
return null;
}
try {
BigDecimal bigDecimal = new BigDecimal(value);
return bigDecimal.scale() > scale ? bigDecimal.setScale(scale, RoundingMode.HALF_UP).stripTrailingZeros() : bigDecimal.stripTrailingZeros();
} catch (NumberFormatException e) {
throw new ConverterException(new FacesMessage(FacesMessage.SEVERITY_ERROR, null, "Message"), e);
}
}
#Override
public String getAsString(FacesContext context, UIComponent component, Object value) {
if (value == null) {
return "";
}
BigDecimal newValue;
if (value instanceof Long) {
newValue = BigDecimal.valueOf((Long) value);
} else if (value instanceof Double) {
newValue = BigDecimal.valueOf((Double) value);
} else if (!(value instanceof BigDecimal)) {
throw new ConverterException("Message");
} else {
newValue = (BigDecimal) value;
}
final String variant = (String) component.getAttributes().get("variant");
if (variant != null) {
if (variant.equalsIgnoreCase("grouping")) {
DecimalFormat formatter = (DecimalFormat) NumberFormat.getNumberInstance();
formatter.setGroupingUsed(true);
formatter.setMinimumFractionDigits(scale);
formatter.setMaximumFractionDigits(scale);
return formatter.format(newValue);
} else if (variant.equalsIgnoreCase("currency")) {
String currency = currencyRateBean.getCurrency();
DecimalFormat formatter = (DecimalFormat) NumberFormat.getCurrencyInstance(new Locale("en", new String(currency.substring(0, 2))));
formatter.setDecimalFormatSymbols(formatter.getDecimalFormatSymbols());
formatter.setCurrency(Currency.getInstance(currency));
return formatter.format(newValue);
}
}
DecimalFormat formatter = (DecimalFormat) NumberFormat.getNumberInstance();
formatter.setGroupingUsed(false); // Not necessary.
formatter.setMinimumFractionDigits(scale);
formatter.setMaximumFractionDigits(scale);
return formatter.format(newValue);
}
}
This can be used as follows.
<h:outputText value="#{bean.value}">
<f:converter converterId="bigDecimalConverter"/>
<f:attribute name="variant" value="grouping"/>
</h:outputText>
Depending upon the value of variant in <f:attribute>, it converts the value to either currency equivalent ($123) or a value using groups (11,111). Other criteria may also be defined as and when required i.e separate converters for other types of formats, if any, are not required. The default task of the converter is to round up the value to two decimal points.
In order to avoid mentioning of,
<f:converter converterId="bigDecimalConverter"/>
or converter="#{bigDecimalConverter}" everywhere, the converter class needs to be decorated with,
#FacesConverter(forClass = BigDecimal.class)
Doing so, JSF takes its own javax.faces.convert.BigDecimalConverter beforehand. I have tried extending that class but it did not make any difference.
Is it possible to override the default JSF javax.faces.convert.BigDecimalConverter so that our own converter can be used by specifying forClass = BigDecimal.class so that there is no need to fiddle around with <f:converter converterId="bigDecimalConverter"/> or converter="#{bigDecimalConverter}" anywhere?
In general, overriding default converters (and validators, components and renderers) can't take place with annotations on the same identifier(s). It really has to be explicitly registered in webapp's own faces-config.xml.
In case you intend to override converter="javax.faces.BigDecimal", then do so:
<converter>
<converter-id>javax.faces.BigDecimal</converter-id>
<converter-class>com.example.YourBigDecimalConverter</converter-class>
</converter>
In case you intend to override implicit conversion for type java.math.BigDecimal, then do so:
<converter>
<converter-for-class>java.math.BigDecimal</converter-for-class>
<converter-class>com.example.YourBigDecimalConverter</converter-class>
</converter>
You need the latter.
Replacing default converters should follow the,uh, following steps:
Extend the default converter that you plan to replace, to avoid class casting exception. In your case this would be the javax.faces.convert.BigDecimalConverter
Register your converter in a faces-config.xml, specifying a <converter-id> that corresponds to the default. This allows you to override the default. What you'll have in effect is:
<converter>
<converter-class>
com.you.YourBigDecimalConverter
</converter-class>
<converter-id>
javax.faces.BigDecimal
</converter-id>
</converter>

Retrieving selectOneMenu complex object as selected item

I'm beginning with JSF (Mojarra 2.2 and Glassfish 4) and currently practicing with a web application which job is to store Clients and their Orders in DB.
When creating a new Order, one feature is to allow choosing an existing client from a JSF <h:selectOneMenu>. An Order entity stores a Client entity among other attributes...
I've followed BalusC's great answer about prepopulating a <h:selectOneMenu> from a DB (here), and have successfully populated mine from data stored in an eager ApplicationScoped ManagedBean, but I can't manage to retrieve the selected item in the backing bean as complex object. It is always null.
This is driving me mad and your help will be truly appreciated!
Here are the relevant code snippets:
#ManagedBean(eager = true)
#ApplicationScoped
public class Data implements Serializable {
private static final long serialVersionUID = 1L;
#EJB
private ClientDao clientDao;
private List<Client> clients;
#PostConstruct
private void init() {
clients = clientDao.lister();
}
public List<Client> getClients() {
return clients;
}
}
Order creation bean (note: 'commande' means order ;)
#ManagedBean
#RequestScoped
public class CreerCommandeBean implements Serializable {
private static final long serialVersionUID = 1L;
private Commande commande;
private String choixNouveauClient = "nouveauClient";
#EJB
private CommandeDao commandeDao;
public CreerCommandeBean() {
commande = new Commande();
}
public void inscrire() {
System.out.println("client : " + commande.getClient()); // prints **NULL**
// ... orderService to store in DB
}
... getters and setters
Client converter:
#FacesConverter(value = "clientConverter", forClass = Client.class)
public class ClientConverter implements Converter {
#Override
public Object getAsObject(FacesContext context, UIComponent component, String value) {
if (value == null) {
return null;
}
Data data = context.getApplication().evaluateExpressionGet(context, "#{data}", Data.class);
for (Client c : data.getClients()) {
if (c.getId().toString().equals(value)) {
return c;
}
}
throw new ConverterException(new FacesMessage(String.format("Cannot convert %s to Client", value)));
}
#Override
public String getAsString(FacesContext context, UIComponent component, Object value) {
return (value instanceof Client) ? String.valueOf(((Client) value).getId()) : null;
}
}
Facelet excerpt:
<p:outputPanel id="gridContainerAncienClient">
<p:selectOneMenu value="#{creerCommandeBean.commande.client}"
rendered="#{creerCommandeBean.choixNouveauClient == 'ancienClient'}">
<f:converter converterId="clientConverter" />
<f:selectItems value="#{data.clients}" var="cli"
itemValue="#{cli}" itemLabel="#{cli.prenom} #{cli.nom}" />
</p:selectOneMenu>
</p:outputPanel>
CreerCommandeBean is #RequestScoped. That means it will live only for one request.
When you select a client to be assigned to #{creerCommandeBean.commande.client} you do this by a request. #{creerCommandeBean.commande.client} is now the selected client. Then the request is over, the bean gets destroyed and your "changes" are lost.
When you try to retrieve that data, you do that by a request again: A new instance of CreerCommandeBean is created and the constructor assigns the property commande with a new instance of Commande whose property client again is probably null.
Solution:
Use a broader scope. e.g. #ViewScoped which makes the bean "live" as long as you stay in the same view - no matter how many requests you make.
Tip:
Read BalusC's Post on Communication is JSF 2.0. Parts might be slightly different in JSF 2.2 but it's still a good and comprehensive introduction.
I got stuck with similar problem, only to realize that I forgot to implement equals() and hashCode() method in my Object. Client Class in this case.
I should blame myself for skipping the instructions in BalusC's blog.
"...Please note the Object#equals() implementation. This is very important for JSF. After conversion, it will compare the selected item against the items in the list. As the Object#equals() also require Object#hashCode(), this is implemented as well...."

Managed property to an Object in a list from a bean

I would do something with a Managed Bean but I dont' find a solution
To explain what I will do I will show a small example:
I have created a Object Data with the following structure
public class Data implements Serializable{
private static final long serialVersionUID = 5156829783321214340L;
String value="";
public Data() {
}
public String getValue() {
return value;
}
void setValue(String data) {
this. value = data;
}
}
As you can see ist a simple dataholder with one property
now I created a secound Object whitch will be my bean it only holds a list of Data Objects
public class Databean implements Serializable{
private static final long serialVersionUID = 9205700558419738494L;
private ArrayList<Data> datalist;
public Databean()
{
datalist = new ArrayList<Data>();
Data newItem;
for (int i=0; i<5; i++) {
newItem = new Data();
datalist.add(newItem);
}
}
public ArrayList<Data> getDatalist() {
return datalist;
}
public void setDatalist(ArrayList<Data> datalist) {
this.datalist = datalist;
}
}
The Declaration in the Faces-config to publish the bean is no Problem
<managed-bean>
<managed-bean-name>managedBean</managed-bean-name>
<managed-bean-class>de.itwu.Databean</managed-bean-class>
<managed-bean-scope>view</managed-bean-scope>
</managed-bean>
So now to my problem:
I would like to create a Managed Property or something else to make a connection to an inputtext
in a repreat control e.g:
<xp:repeat value="#{managedBean.datalist}" var="rowData">
<xp:inputText id="inputText1" defaultValue="#{rowData.value}"></xp:inputText>
</xp:repeat>
does anyone have an Idea how this could work?
So exmaple corrected but it doesen't work the Ich ich set Datualt values in the Data-Object they are shown. But when I edit the values in the Inputtextfields they are not automatically written back to the Object. I Thing the Problem is the Daclaration in the Faces-Config. Ideas?
The variable assigned in the repeat to var (rowData) will contain an instance of your Data class. To bind each input control to the value field you refer to that property. Because you have a getValue() and setValue() defined a value binding will be created and you will be able to edit the content. If only a getValue() method is defined a method binding is created and the field will not be editable.
<xp:repeat value="#{managedBean.datalist}" var="rowData">
<xp:inputText id="inputText1" value="#{rowData.value}"></xp:inputText>
</xp:repeat>
Your binding is wrong.
<xp:repeat value="#{managedBean.datalist}" var="rowData">
<xp:inputText id="inputText1" defaultValue="#{rowData.value}"></xp:inputText>
</xp:repeat>
rowData contains Data object, which populates getter/setter for field value, not datavalue.

Resources