PrimeFaces selectOneMenu with custom editable inputField - jsf

Silly question, but that's my situation. I am having editable PrimeFaces selectOneMenu where inputField has following restriction:
lower and upper limit of number typed in
predefined text allowed
when decimal number is being typed allow only 2 decimal numbers
All is good except the last one with decimal number restriction. What it means is that I can't type there 1.111 but only 1.11. Change event keyUp for selectOneMenu is sadly added to the tag select but not to input.
Any ideas how to solve?

This calls for a custom validator. Create one which checks for the predefined values, if no match is found, check the number format. Basic example:
#FacesValidator("myValidator")
public class MyValidator implements Validator
{
private List<String> predefinedValues = Arrays.asList("my", "list");
#Override
public void validate(FacesContext context, UIComponent component, Object value)
throws ValidatorException
{
String valueStr = (String) value;
// Check if value is predefined
if (predefinedValues.contains(valueStr)) {
return;
}
// If not predefined, check number format
if (! valueStr.matches("^\\d+(\\.\\d\\d?)?$")) {
throw new ValidatorException(new FacesMessage("Value is invalid!"));
}
// Check number limits...
}
}
The validator can be used in your XHTML as:
<p:selectOneMenu editable="true" ...>
...
<f:validator validatorId="myValidator" />
</p:selectOneMenu>
As an alternative you could use jQuery to find the input field and bind a keypress listener to it. See for example: Jquery: filter input on keypress. However, I would keep the validator in place. Users could paste text for example.
See also:
How to perform validation in JSF, how to create a custom validator in JSF
Regular expression for floating point numbers

Related

Values converted by custom converter not displayed

I am using a PrimeFaces' pickList with a custom converter.
JSF:
<p:pickList converter="costsConverter" value="#{offerController.costsAsDualListModel}" var="cost" itemLabel="#{cost}" itemValue="#{cost}" />
offerController.costsAsDualListModel looks like this:
public DualListModel<Cost> getCostsAsDualListModel() {
DualListModel<Cost> costsDualList;
List<Cost> costsSource = new ArrayList<Cost>();
List<Cost> costsTarget = new ArrayList<Cost>();
for (Cost c : costs) {
costsSource.add(c);
}
costsDualList = new DualListModel<Cost>(costsSource, costsTarget);
return costsDualList;
}
And my custom converter looks like this:
public String getAsString(FacesContext context, UIComponent component, Object object) {
if (object == null) {
return "";
}
Integer no = ((Cost) object).getNo();
String valueOf = String.valueOf(no);
return valueOf;
}
getAsString() is called and valueOf is correct but inside my picklist I still see the objects and not the return value fo getAsString().
I tried to use <f:converter converterId="costsConverter"/> within the picklist element. Same issue. Also I registered the converter in faces-config.xml:
<converter>
<converter-id>costsConverter</converter-id>
<converter-class>com.my.converter.CostsConverter</converter-class>
</converter>
What could be the problem?
You have a wrong understanding of values in components like picklists, selectonemenus, etc. These values are never displayed there but the labels are. And since converters are for values, not labels, you'll never see the converted value but the labels and everything behaves as it should. Just use itemLabel="#{cost.no}" and everything should be fine (display wise).
See e.g. how it is used in these two Q/A that also use a converter
How to write a custom converter for <p:pickList>
Primefaces Picklist Converter

How to use SelectManyCheckbox with two ArrayList? - Primefaces

I'm trying to implement a <p:selectManyCheckbox> but I'm having no success.
Now I have the following architecture:
Course - have many Disciplines
Discipline - belongs to none, one or many Courses.
In Course class I have two ArrayList<Discipline>:
public class CourseMBean{
(...)
// Stores all disciplines
private static ArrayList<Discipline> allDisciplines;
// Stores only the disciplines that's already associated with this course.
private static ArrayList<Discipline> courseDisciplines;
(get and set for the arraylists)
(...)
}
All data comes from a MYSQL DB, but that isn't the question. Now I want create a new Course, so I don't have anything in courseDisciplines.
I want show allDisciplines in checkboxes, and want that when user select one checkbox, the object Discipline of this checkbox be added in courseDisciplines - and when unselect one checkbox, remove the discipline from the courseDsiciplines.
My JSF 2.0 code is following:
<p:selectManyCheckbox id="disciplines" value="#{courseMBean.allDisciplines}" layout="grid" columns="2">
<f:selectItems value="#{courseMBean.courseDisciplines}" />
</p:selectManyCheckbox>
This actually shows all disciplines without any selected checkboxes, what's right. But when I select some checkboxes and submit the form I try to print the elements inside courseDisciplines, and this don't show anything in console.
What I'm doing wrong?
when I select some checkboxes and submit the form I try to print the elements inside courseDisciplines
As the courseDisciplines actually represents the available items not the selected items, it seems that you misunderstood some basic concepts around the UISelectMany and UISelectItem(s) components.
The <f:selectItem(s)> (from the UISelectItem(s) family) represent the available items. It are exactly those items which are shown in the UI and which the enduser has to choose from.
The value attribute of <p:selectManyCheckbox> (from the UISelectMany family, like <h:selectManyCheckbox> and <h:selectManyMenu>) represent the (pre)selected items. If this is null or empty during first display of the form, then nothing is preselected. Or if this contains some preselected items, then only those available items which are equal() will be preselected.
When the enduser has changed the selection in the UI and submits the form, then all selected items will end up in the value attribute of UISelectMany component. The UISelectItem(s) remains unchanged.
Here's a basic kickoff example:
<p:selectManyCheckbox value="#{bean.selectedItems}">
<f:selectItems value="#{bean.availableItems}" />
</p:selectManyCheckbox>
<p:commandButton value="Submit" action="#{bean.submit}" />
<p:messages autoUpdate="true" />
private List<String> selectedItems; // +getter +setter
private List<String> availableItems; // +getter (no setter necessary!)
#PostConstruct
public void init() {
availableItems = new ArrayList<String>();
availableItems.add("one");
availableItems.add("two");
availableItems.add("three");
}
public void submit() {
System.out.println("Selected items: " + selectedItems);
}
(all other <p:selectManyXxx> and <h:selectManyXxx> components work exactly the same)
When a complex Javabean object like Discipline comes into the picture, then you need to make sure that there's a Converter for that so that JSF can properly convert between it and String for usage in generated HTML output and as HTTP request parameter (HTML and HTTP namely can't pass around nor hold Java objects, but only character sequences which are in Java represented by String).
This is perhaps your root problem. You said that nothing is printed to the console on submit. But it could be as good the case that the whole submit() method is actually never being invoked. You're not explicit enough on this. If the whole action method is indeed never invoked (i.e. a debug breakpoint doesn't hit there, or another System.out.println() printing a static string is never shown in console), then you've actually most likely a conversion error. If you have used <h|p:message(s)> the right way, or have paid attention to server log about queued but undisplayed faces messages, then you should have noticed it.
In that case, you need to implement a Converter which converts between Discipline and String.
#FacesConverter(forClass=Discipline.class)
public class DisciplineConverter implements Converter {
#Override
public Object getAsObject(FacesContext context, UIComponent component, String submittedValue) {
// Write code here to convert from String to Discipline.
}
#Override
public String getAsString(FacesContext context, UIComponent component, Object modelValue) {
// Write code here to convert from Discipline to String.
}
}
More than often the DB ID is being used as String representation. See also the section "Complex object as selected item" of this answer on a related question: How to populate options of h:selectOneMenu from database?

JSF outputlabel clear

I have the following problem. I use a form when I provide only PIN. I have validator which checks if its a 4-digit number. Then the action on submit is set to the method which checks if the PIN exists in the database. If not it does message = "no PIN"; I used the message in the output label below the form. Previously it was null so there was no message there. Now it changes into "no PIN" but I have to clear it after clicking the submit button again because the error message doesn't disappear when you enter for example "12as" PIN and validator takes care of it. How should i implement such situation? Maybe using an output label in such situtation is a wrong idea?
You should not perform validation in action method. You should use a real validator.
Just implement the Validator interface accordingly. E.g.
#FacesValidator("pinValidator")
public class PinValidator implements Validator {
#Override
public void validate(FacesContext context, UIComponent component, Object value) throws ValidatorException {
String pin = (String) value;
if (pin == null || pin.isEmpty()) {
return; // Let required="true" deal with it if necessary.
}
if (!pin.matches("\\d{4}")) {
throw new ValidatorException(new FacesMessage("PIN must be 4 digits"));
}
if (!somePinService.exists(pin)) {
throw new ValidatorException(new FacesMessage("PIN is unknown"));
}
}
}
Use it as follows:
<h:outputLabel for="pin" value="PIN" />
<h:inputText id="pin" value="#{bean.pin}" validator="pinValidator" />
<h:message for="pin" />
The faces message of the validator exception will end up in the <h:message> associated with the component on which the validator is been fired.
If you're using ajax to submit the form, don't forget to make sure that the message is also taken into account on ajax render.
Unrelated to the concrete problem, the JSF <h:outputLabel> generates a HTML <label> element which is intented to label a form element (e.g. <input>, <select>, etc). It's absolutely not intented to show an arbitrary piece of text such as a validation message. I recommend to put JSF aside for now and start learning basic HTML. This way you will understand better which JSF components to pick to get the desired HTML output.
You can use JSF message component outside a validator:
For a message for your input in your form:
<h:message for="PIN"/>
And at your managed bean you can add a FacesMessage using:
FacesMessage message = new FacesMessage(FacesMessage.SEVERITY_WARN,"No pin summary message","No pin detail message");
FacesContext.getCurrentInstance().addMessage("PIN", message);
No need to use a outputLabel here.

JSF: How to validate an inputText only when another component is selected

I have three radio buttons, an inputText and a submit button. I only want to validate the input text on submit when a certain radio is selected. So I have
<h:inputText validator="#{myBean.validateNumber}" ... />
And in my bean I have
public void validateNumber(FacesContext context, UIComponent component,
Object value) throws ValidatorException{
if(selectedRadio.equals("Some Value"){
validate(selectedText);
}
}
public void validate(String number){
if (number != null && !number.isEmpty()) {
try {
Integer.parseInt(number);
} catch (NumberFormatException ex) {
throw new ValidatorException(new FacesMessage(FacesMessage.SEVERITY_ERROR,
"Error", "Not a number."));
}
} else {
throw new ValidatorException(new FacesMessage(FacesMessage.SEVERITY_ERROR,
"Error", "Value is required."));
}
}
The one thing that make this not work is that when I submit, validateNumber(...) runs before my setter method for the radio button setSelectedRadio(String selectedRadio). Therefore causing this statements
if(selectedRadio.equals("Some Value"){
validate(selectedText);
}
to not execute correctly. Any idea on how to get around this problem?
The selectedRadio is as being a model value only updated during update model values phase, which is after the validations phase. That's why it's still the initial model value while you're trying to examine it.
You'd have to grab it from either the request parameter map (which is the raw submitted value), or the UIInput reference, so that you can get either the submitted value by getSubmittedValue() or the converted/validated value by getValue().
So,
String selectedRadio = externalContext.getRequestParameterMap().get("formId:radioId");
or
UIInput radio = (UIInput) viewRoot.findComponent("formId:radioId"); // Could if necessary be passed as component attribute.
String submittedValue = radio.getSubmittedValue(); // Only if radio component is positioned after input text, otherwise it's null if successfully converted/validated.
// or
String convertedAndValidatedValue = radio.getValue(); // Only if radio component is positioned before input text, otherwise it's the initial model value.
It is called cross-field validation (validate not only based in the value of a component, but a set of them).
Currently, JSF2 does not support it ( JSF doesn't support cross-field validation, is there a workaround? ) but there are several libraries (in the refered question omnifaces is mentioned, it looks like seamfaces also has something for it) that might help. Also in the question there is a workaround.

JSF issue about space

I am a green bird in JSF, now I was puzzled by the text of it.
when I assign the value of the outputText with multi-spaces, the result shown in IE has only one space.
ex: the code is like
<h:outputText id="name" value="aa (multi-spaces here) bbb" ></h:outputText>
the result text shown in IE is "aa bbb"
can anyone tell me why the spaces disappear without trace?
This behaviour is defined by the HTML spec:
For all HTML elements except PRE, sequences of white space separate "words" (we use the term "word" here to mean "sequences of non-white space characters"). When formatting text, user agents should identify these words and lay them out according to the conventions of the particular written language (script) and target medium.
Note that if you are using XHTML, there are some differences in how attributes and the code point U+000C are handled.
For most text, sequences of white space are not rendered any differently from a single space.
Since this is for a outputText control, you can use a one-way converter for a no-break space solution:
package myconverters;
// imports
public class SpacePreserver implements Converter {
private static final char NO_BREAK_SPACE = '\u00A0';
public String getAsString(FacesContext context, UIComponent component,
Object value) {
if (component instanceof EditableValueHolder) {
throw new IllegalArgumentException(
"Cannot use SpacePreserver converter on editable controls.");
}
return value == null ? null : value.toString().replace(' ', NO_BREAK_SPACE);
}
public Object getAsObject(FacesContext context, UIComponent component,
String value) {
throw new UnsupportedOperationException("Output converter only");
}
}
This can be defined (among other ways) using a faces-config.xml entry:
<converter>
<converter-id>spacePreserver</converter-id>
<converter-class>myconverters.SpacePreserver</converter-class>
</converter>
You can then add this to your output control:
<h:outputText id="text1" value="a b c" converter="spacePreserver" />
This code was tested using JSF 1.1 with a UTF-8 encoded JSP 2.0 view. Note that use of a no-break space will prohibit line-wrapping.

Resources