How to use SelectManyCheckbox with two ArrayList? - Primefaces - jsf

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?

Related

PrimeFaces selectOneMenu with custom editable inputField

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

Illegal Syntax for Set Operation: How to tell JSF I don't "want" a setter

This question may be more of the type "conceptual" or "I don't understand JSF".
My scenario:
I have a JSF Page (index.xhtml) where I use a p:accordionPanel (but I don't think it matters what component it is). What I want to do is to set the activeIndexes of it.
<p:accordionPanel multiple="true" activeIndex="#{myController.getActiveIndexesForSections('whatever')}">
// bla bla...
</p:accordionPanel>
And the (simplified) method in the backing bean:
public String getActiveIndexesForSections(String holderName){
String activeSections = "";
for(Section s : sectionMap.get(holderName)){
if (s.isActive())
//add to the string
}
return activeSections;
}
Now this works just fine on a normal page load.
But if I click on a p:commandButton (with ajax=false) (or anything else which "sends" data back to the server I guess) - I get the following exception:
/WEB-INF/tags/normalTextSection.xhtml #8,112 activeIndex="#{myController.getActiveIndexesForSections(name)}": Illegal Syntax for Set Operation
// bla..
Caused by: javax.el.PropertyNotWritableException: Illegal Syntax for Set Operation
After some googling / reading the error message I found that I need a setter.
First of all: I don't want a setter - do I really need one or is there a way to tell JSF I don't want this "behavior".
Second I realized that it's not that "easy" to provide a setter, because my method has a parameter (so public void setActiveIndexesForSections(String name, String activeIndexes) or public void setActiveIndexesForSections(String name)won't work).
What I came up with in the end is:
Create a (generic) "Pseudo-Property-class":
// just a dummy class since the class is recreated at every request
public class Property<T> implements Serializable {
private T val;
public Property(T val) {
this.val= val;
}
public T getVal() {
return val;
}
//no need to do anyhting
public void setVal(T val) {
}
}
Change the bean method:
public Property<String> getActiveIndexesForSections(String holderName){
String activeSections = "";
for(Section s : sectionMap.get(holderName)){
if (s.isActive())
//add to the string
}
return new Property<String>(activeSections);
}
And call it from the index.xhtml:
<p:accordionPanel multiple="true" activeIndex="#{myController.getActiveIndexesForSections('whatever').val}">
// bla bla...
</p:accordionPanel>
This works but obviously is a ugly hack/workaround.
What is the proper way to handle a situation like this? Or is what I'm doing simply completely wrong?
The setter is needed to remember the active indexes as they were when the form is submitted. Basically, you need to bind it as a value expression (with a property), not as a method expression (like an action method), nor to an unmodifiable collection (like activeIndex="#{param.tab}"). Exactly like as with input values. Technically, you're indeed doing it "simply completely wrong" ;)
The requirement is however understood. Given that you're really not interested in the changed active indexes, and thus want to reset them to defaults on every form submit, then you can bypass it by storing the result as a request attribute with help of <c:set>. This way you will fool EL to set it in the request attribute map instead of the intented bean property.
<c:set var="activeIndex" value="#{myController.getActiveIndexesForSections('whatever')}" scope="request" />
<p:accordionPanel multiple="true" activeIndex="#{activeIndex}">
<!-- bla bla... -->
</p:accordionPanel>
Under the covers, it will basically do externalContext.getRequestMap().put("activeIndex", value) as setter operation, which will obviously just work.
Update: upon inspecting the source code of AccordionPanel component, I saw another workaround given the fact that the activeIndex won't be set when the rendered attribute evaluates false. So just alter the rendered attribute to behave exactly that: evaluate false during update model values phase (the 4th phase).
<p:accordionPanel multiple="true"
activeIndex="#{myController.getActiveIndexesForSections('whatever')}"
rendered="#{facesContext.currentPhaseId.ordinal ne 4}">
<!-- bla bla... -->
</p:accordionPanel>

JSF multiple groups of selectOneRadio on a page: how to recover the values?

I have a jsf page with multiple radiobutton groups (dynamically generated) on it. I need to retrieve the values from it in a backing bean, but fails to do so.
The business: a user wants to subscribe to a course that consists of multiple groups of coursedays. The user can choose the coursedays. So if a course consists of for example 4 coursedays, organised in 3 different groups, the user can choose from 12 coursedays, in blocks of 3.
The relevant part of the xhtml-page:
<c:forEach var="cd1" items="#{coursedayBean.getCoursedays(groupBean.getFirstGroup}">
<h:selectOneRadio value="#{subscriptionBean.selectedCoursedays[cd1.sequenceNr]}" >
<f:selectItems value="#{coursedayBean.getCoursedaysSelectItems}"/>
</h:selectOneRadio>
</c:forEach>
This results in a n*m matrix of radiobuttons where I want to retrieve n values.
The selectItems are of type <Long, String>.
In my backing bean, I declared the following:
public List<String> getSelectedCoursedays() {
return selectedCoursedays;
}
public void setSelectedCoursedays(List<String> selectedCoursedays) {
this.selectedCoursedays = selectedCoursedays;
}
I tried with a Map, List, but none of them worked. The setSelectedCoursedays is never called.
How do I declare the array/list/map to get the values in my backing bean?
#{subscriptionBean.selectedCoursedays[cd1.sequenceNr]}
doesn't do the trick.
This construct should work just fine. The setter will indeed never be called. JSF/EL just calls the setter on ArrayList itself by the add(index, object) method. I.e. it does basically:
subscriptionBean.getSelectedCoursedays().add(cd1.sequenceNr, selectedItem);
I'm not sure how you observed the concrete problem of "it doesn't work". Perhaps you were firing an ajax request and putting a breakpoint on the setter method and didn't read the server logs. There are two possible cases where this construct will fail.
If you don't prepare the selectedCoursedays with new ArrayList(), then you will get PropertyNotFoundException: Target Unreachable, 'selectedCoursedays' returned null.
If you don't fill the selectedCoursedays with the same amount of null items as the course days, then you will get an ArrayIndexOutOfBoundsException
So, you should prepare the selectedCoursedays as follows:
#PostConstruct
public void init() {
selectedCoursedays = new ArrayList<String>();
for (int i = 0; i < coursedays.size(); i++) {
selectedCoursedays.add(null);
}
}
Easier alternative is to make it a String[].
private String[] selectedCoursedays;
#PostConstruct
public void init() {
selectedCoursedays = new String[coursedays.size()];
}
It is miss understanding between c:forEach and ui:repeat. c:forEach will not build UI component tree nodes. Firstly, you have to reference difference between them here.

Display all rows in tomahawk paginated, sortable datatable

I'm trying to create a paginated, sortable datatable using MyFaces. The sorting works fine; I can click on a column header and it will sort the data based on the column. Also the pagination works fine for the most part. The datatable will split itself appropriately with some number of items per page. In addition, I want the user to be able to change the number of items displayed per page. Again, this seems to be working until I want all of the items displayed on one page.
According to this reference (also here), if you set the "rows" attribute of t:datatable to "0", it will display the remaining rows in the table. However, when I try this, I get an exception that includes this message:
javax.faces.FacesException - You need to set a value to the 'rows' attribute of component 'myComponent'
I'm trying to set the number of items per page using an attribute in a backing bean. My t:datatable looks like this:
<t:dataTable id="myComponent" var="cur"
value="#{backingBean.list}" sortAscending="#{backingBean.ascending}"
sortColumn="#{backingBean.sortColumn}" sortable="true"
styleClass="myClass" rowClasses="oddRow,evenRow"
rows="#{backingBean.itemsPerPage}" preserveDataModel="false">
<!-- data here -->
</t:datatable>
Later, I have a t:dataScroller to control the pagination:
<t:dataScroller id="pageNavigation" for="myComponent"
paginatorActiveColumnStyle="font-weight:bold;"
renderFacetsIfSinglePage="false"
binding="#{backingBean.scroller}"
paginator="true" >
<!-- facets here -->
</t:dataScroller>
Then, I have a h:selectOneMenu to select the number of items per page
<h:selectOneMenu id="myScroller"
value="#{backingBean.itemsPerPage}"
required="true" onchange="this.form.submit();"
valueChangeListener="#{backingBean.updateItemsPerPage}">
<f:selectItems value="#{backingBean.itemsPerPageArray}" />
</h:selectOneMenu>
My backing bean looks something like this:
public class BackingBean {
private boolean ascending;
private Long itemsPerPage;
private String sortColumn;
private ArrayList<SelectItem> itemsPerPageArray;
private ArrayList<SomeObject> list; // data for table
private HtmlDataScroller scroller;
// constructors, getters, setters, and other stuff here
public void updateItemsPerPage(ValueChangeEvent valueChangeEvent) {
itemsPerPage = (Long) valueChangeEvent.getNewValue();
resetScrollerIndex();
}
private void resetScrollerIndex() {
if (scroller!=null && scroller.isPaginator())
scroller.getUIData().setFirst(0);
}
// called in constructor
private void constructItemsPerPageArray() {
itemsPerPageArray = new ArrayList<SelectItem>();
itemsPerPageArray.add(new SelectItem(new Long(10), "10"));
itemsPerPageArray.add(new SelectItem(new Long(50), "50"));
itemsPerPageArray.add(new SelectItem(new Long(100), "100"));
itemsPerPageArray.add(new SelectItem(new Long(0), "All"));
}
}
To sum up, when I select the "All" item from the h:selectOneMenu, I get the exception mentioned above. Hopefully, I've included an appropriate level of detail. Thanks!
Alright, I think I found the answer...
If I remove the first line from the updateItemsPerPage method, the problem is fixed. The method now looks like:
public void updateItemsPerPage(ValueChangeEvent valueChangeEvent) {
resetScrollerIndex();
}
It thought I had tried that...

Unable to retrieve the value of <h:selectBooleanCheckbox> inside my managed bean's action method

JSF view code:
<f:view>
<h:form>
<h:panelGrid>
<h:inputText id="key" value="#{myManagedBean.key}"/>
<h:selectBooleanCheckbox id="rerun" value="#{myManagedBean.rerun}" rendered="#{myManagedBean.displayRerun}"/>
<h:commandButton id="check" action="#{myManagedBean.check}"/>
</h:panelGrid>
</h:form>
<f:view>
JSF model code:
public class MyManagedBean {
private boolean displayRerun;
public void setDisplayRerun(boolean aDisplayRerun) {
this.displayRerun = aDisplayRerun }
public boolean getDisplayRerun() {
return this.displayRerun;
}
private String key;
public void setKey(String aKey) {
this.key = aKey
}
public String getKey() {
return this.key;
}
private boolean rerun;
public void setRerun(boolean arerun) {
this.rerun = arerun
}
public boolean getRerun() {
return this.rerun;
}
public String check() {
//do data validation
setDisplayRerun(true);
System.out.println(getRerun());
}
}
This always prints false regardless of whether the checkbox is checked or not.
Additional Information on my requirement:
Nick/BalusC, my managed bean is of request scope. It is indeed simplified code snippet that I presented. My page has couple of user input controls along with a command button. On submit of command button, I call action method of backing bean, in which I do data validation (in this case I lookup database and see if the inputs are already registered.) If already registered, I come back to the same page, this is when I display the singleBooleanCheckBox for the user to select and hit the command button again.
I am toggling the display of the checkbox based on a managedbean property (a boolean flag set during data validation).
When I re-submit the page with checkbox checked, I do not receive this data.
For further verification, I replace the selectBooleanCheckbox, with a command button with similar behavior (basically do not render it initially, but only show it on data validation). I mapped its #action to my managedbean's action method. To my surprise, when I hit the button, the action method is not executed. Instead, the page is refreshed like in a "immediate" scenario or a redirect.
I have been struggling on this for almost 6 hrs. Appreciate your experienced insights.
Thanks.
So, you've actually a rendered attribute on the checkbox (that was not present in your initial question) and the bean is request scoped (it would have worked when it was session scoped). The submitted checkbox value will not be gathered during apply request values phase when this attribtue evaluates false at that point.
You basically need to retain the condition responsible for the rendered attribute in the subsequent request as well. This can be done in several ways: putting bean in session scope, using Tomahawk's t:saveState or h:inputHidden with a bean binding. Each is outlined in detail in this answer.

Resources