I'm creating a List of javax.faces.model.SelectItem (in a bean) for use with a h:selectManyCheckbox but I cannot figure out how to make a SelectItem selected.
How to do this? Must be possible, right?...
public List<SelectItem> getPlayerList(String teamName) {
List<SelectItem> list = new ArrayList<SelectItem>();
TeamPage team = (TeamPage) pm.findByName(teamName);
List<PlayerPage> players = pm.findAllPlayerPages();
for (PlayerPage player : players) {
boolean isMember = false;
if (team.getPlayerPages().contains(player)) {
isMember = true;
}
SelectItem item;
if (isMember) {
// TODO: Make SelectItem selected???
item = null;
} else {
item = new SelectItem(player.getId(), createListItemLabel(player), "", false, false);
}
list.add(item);
}
return list;
}
Assume we have this JSF code:
<h:selectManyCheckbox value="#{bean.selectedValues}">
<f:selectItems value="#{bean.playerList}"/>
</h:selectManyCheckbox>
then the selected values (i.e. the checked checkboxes) are stored in the bean.selectedValues property.
Thus, in your Java code, you must handle the selectValues by putting the correct ID in the selectedValues property.
If anybody is working with selectOneMenu and populating elements dynamically:
While creating a SelectItem, if you provide some value(first parameter) as follows:
new SelectItem("somevalue", "someLabel", "someDescription", false, false);
It translates to this in the html:
<option value="somevalue">someLabel</option>
If you do not provide a value as follows:
new SelectItem("", "someLabel", "someDescription", false, false);
, it translates to this
<option value="" selected="selected">someLabel</option>
Hence if you want an element to be the default on page load(like "Select one of these"), do not provide a value. If you create more than one element without a value, any one of them are chosen for default on page load(probably preference based on ascending alphabetical order).
Related
I have looked around and could not find a solution.
I am using Omnifaces listConverter in the PickList component of Primefaces. When I move an item from SOURCE to TARGET. In the backing bean i get the new item ONLY on dualList.getTarget().
However when I move an item from TARGET to SOURCE. In the backing bean I am not able to check whether an item has been removed from the TARGET.
I tried dualList.getSource() - SOURCE does not contains the item/s dropped from TARGET
And off course dualList.getTarget() will be empty (assuming that no item is moved from SOURCE to TARGET).
My question is how can i find that something has been moved from TARGET to SOURCE in the backing bean?
Previously I have created a custom converter and in that, my dualList.getTarget() gives me the updated target (ie old values + new values added + old values removed). Therefore I can figure it out which values are added, removed and are still present.
But while using Omnifaces listConverter i am not understanding how to achieve it.
My code looks like this:
XHTML
<p:pickList id="pickList" value="#{assignBenchmarkersBean.dualUserList}"
var="users" itemLabel="#{users.username}" itemValue="#{users}" >
<o:converter converterId="omnifaces.ListConverter"
list="#{assignBenchmarkersBean.dualUserList.source}" />
</p:pickList>
<p:commandButton id="update" value="#{bundle.create}"
styleClass="redButton bigFont"
actionListener="#{assignBenchmarkersBean.update()}" />
Bean
public void update(){
try{
prepareRemoveOldAndAddNewUsersList();
/**some other code **/
}catch(Exception e){
FacesMsg.error(ErrorMessage.UPDATE.getValue(), e);
}
}
private void prepareRemoveOldAndAddNewUsersList() {
for(User user : dualUserList.getTarget()){ //add users
addUsers.add(user);
}
for(User user : dualUserList.getSource()){ //remove users
if(oldUserList.contains(user)){
removeUsers.add(user);
}
}
}
User entity
#Override
public int hashCode() {
int hash = 0;
hash += (id != null ? id.hashCode() : 0);
return hash;
}
#Override
public boolean equals(Object object) {
// TODO: Warning - this method won't work in the case the id fields are not set
if (!(object instanceof User)) {
return false;
}
User other = (User) object;
if ((this.id == null && other.id != null) || (this.id != null && !this.id.equals(other.id))) {
return false;
}
return true;
}
#Override
public String toString() {
return "com.abc.model.main.User[ id=" + id + " ]";
}
NOTE:
If in the converter's list attribute, I put bean.dualList.target, then the whole thing works the other way around.
It is better to use ajax transfer event for this goal, as described in PrimeFaces User Guide.
XHTML:
<p:pickList id="pickList" value="#{assignBenchmarkersBean.dualUserList}"
var="users" itemLabel="#{users.username}" itemValue="#{users}" >
<o:converter converterId="omnifaces.ListConverter"
list="#{assignBenchmarkersBean.dualUserList.source}" />
<p:ajax event="transfer" listener="#{pickListBean.handleTransfer}" />
</p:pickList>
Bean:
public class PickListBean {
//DualListModel code
public void handleTransfer(TransferEvent event) {
//event.getItems() : List of items transferred
//event.isAdd() : Is transfer from source to target
//event.isRemove() : Is transfer from target to source
}
}
This is how I got it worked for me.
In the implementation of the Omnifaces ListConverter, the getAsObject method is using the list to find the value and update the target and source of dualList.
NOTE: you will get either a new value added in target (List=".getSource") or a new value added in source (List=".getTarget"), as per the List value you have entered.
Hence by changing the List from myBean.dualList.getSource to myBean.completeList, I get the updated target and source (ie old values + new values added + old values removed ).
If a user is only concerned with the new value added in target or source and not on if any value is removed, then he should use myBean.dualList.getSource and myBean.dualList.getTarget respectively.
However if user wants to know the final content as it is in my case, use the whole collection ie myBean.completeList.
In short it is the List which you are passing in the <o:conveter> tag is important.
Hope it will help!!
I am trying to add SelectItem objects to Set collection. However, duplicate values are being added. Is there any way to prevent these duplicate values?
for(String s: list) {
Set<SelectItem> typeSet = new HashSet<SelectItem>();
typeSet.add(new SelectItem(s));
}
Just use Set<String> instead of Set<SelectItem>. The SelectItem#equals()/#hashCode() isn't implemented at all to take the actual value into account.
private Set<String> typeSet;
#PostConstruct
public void init() {
List<String> list = getItSomehow();
typeSet = new LinkedHashSet<String>(list);
}
It's since JSF2 just usable on <f:selectItems> as well.
<f:selectItems value="#{bean.typeSet}" />
See also:
Our selectOneMenu wiki page
I really would need your help in solving this problem am having with my project. Anyway, am new to JSF. Am working on a project and there is this module where examination records are displayed to students from a database upon successful authentication. This would be easy if all students are in the same class and offer the same number of courses, as you can create a dataTable model and then use it in your view. But in my case, the database contains tables for student’s exam records in different departments and classes like Computer Science 100,200 and 300 levels, Geology 100,200 and 300 levels and so on. These students also offer entirely different courses. Some offer physics, GST, computer and some don’t.
My problem is how do I display to different students their grades and course names in a dataTable dynamically. Let me try and picture it.
100 level Computer Science table has
CS101 CS102 GST101 MATH101 MATH102 PHY101 CHM101
200 level Computer Science table has
CS201 CS202 CS203 MATH201 CHM202
Please note the difference in the number of columns and names of column headers.
Then a Computer Science 100 level student wants to view his or her exam records, how do I display these records with the column names in a dataTable dynamically and not first creating a dataTable model with predefined column names. Not knowing the number of columns or names in advance. How do you do this for different students in different classes? I want a good code that will enable students from any department or level to login and then retrieve his exam records. Am not asking for the authentication code as that has been achieved, the code will display the names of the Database Table column headers and the records they hold for a particular 100 or 200 level. I need this because I don’t have to create a dataTable model containing specific column headers as i have many tables in the Database with different numbers of columns and names for different students departments and classes. Please I need your help and I hope I’ve been able to communicate my problem clearly. Am using JSF 2.1 (Facelets and Manage Bean), MySQL Database Server on NetBeans 7.2.1.
I was in a similar situation and I am afraid that there's not possible way to fill a Datatable with unknown columns. You have to have the numbre in advance.
In my case, I found it easier to shift to JSP for this particular need and keep JSF for more "static" usage.
In my understanding, the only dynamic aspect of a Datatable is the numbre of rows.
One way to turn around this problem is using the rendered attribute of columns, in this case you'll put in every possible column and decide (via a bean, for example) to show or not some of them and hide some others. This is semi-dynamic at best, but still is a livable solution.
Best of luck.
P.S : if wrong, please inform me of the solution because I would be curious to know it though unable to use it.
How about creating a two columned table
First column will be named: Course Code/Name
Second column will be name: Grade
Thats how it is being displayed in the Uni/College anyway...
To implement it just create an object with 2 Strings and populate a list of such objects... and that will be your h:datatable value attribute...
You can use List<Map<String, Object>> to get hold of the data in a generic manner. Then you can use <c:forEach> to dynamically build columns (please note that <ui:repeat> is insuitable as it doesn't run during view build time, but during view render time).
Here's a concrete kickoff example, assuming that your environment supports EL 2.2:
<h:dataTable value="#{bean.listOfMaps}" var="map">
<c:forEach items="#{bean.listOfMaps[0].keySet().toArray()}" var="key">
<h:column>
#{map[key]}
</h:column>
</c:forEach>
</h:dataTable>
If you need the column labels in a separate collection as well (a LinkedHashMap<String, String> is recommended for that as it maintains ordering), then instead do so:
<h:dataTable value="#{bean.listOfMaps}" var="map">
<c:forEach items="#{bean.mapOfColumns}" var="column">
<h:column>
<f:facet name="header">#{column.value}</f:facet>
#{map[column.key]}
</h:column>
</c:forEach>
</h:dataTable>
you can create column at runtime using backing bean method
public void loadDynamicList() throws Exception {
// Set headers (optional).
//dynamicHeaders = new String[] {"ID", "Name", "Value","delete"};
// Set rows. This is a stub example, just do your dynamic thing.
dynamicList = new ArrayList<String>();
/*dynamicList.add("One");
dynamicList.add("Two");
dynamicList.add("Three");
dynamicList.add("Four");
dynamicList.add("Five");
dynamicList.add("Six");*/
/*dynamicList.add(Arrays.asList(new String[] { "ID1","ID2" }));
dynamicList.add(Arrays.asList(new String[] { "ID1","ID2" }));
dynamicList.add(Arrays.asList(new String[] { "ID1","ID2" }));
dynamicList.add(Arrays.asList(new String[] { "ID1","ID2" }));
dynamicList.add(Arrays.asList(new String[] { "ID1","ID2" }));*/
existingCountryList = new ArrayList<Country>();
String countryCode="SL";
existingCountryList.add(getCountryService().getCountryByCode(countryCode));
Country country=getCountryService().getCountryByCode(countryCode);
countryLanguageSet=country.getCountryLanguage();
int languageSize=country.getCountryLanguage().size();
dynamicHeaders = new String[languageSize+1] ;
int counter=0;
for (CountryLanguage count: countryLanguageSet) {
System.out.println(count.getLanguage().getLanguageName());
dynamicHeaders[counter]=count.getLanguage().getLanguageName();
counter++;
}
dynamicHeaders[counter]="Delete";
System.out.println("header list "+dynamicHeaders.toString());
System.out.println("size"+dynamicHeaders.length);
}
public void populateDynamicDataTable() {
debugLogger.debug("populateDynamicDataTable:Enter");
// Create <h:dataTable value="#{myBean.dynamicList}" var="dynamicItem">.
HtmlDataTable dynamicDataTable = new HtmlDataTable();
dynamicDataTable.setValueExpression("value", createValueExpression("# {relationBean.dynamicList}", List.class));
dynamicDataTable.setVar("dynamicItem");
for (int count = 0; count < dynamicHeaders.length; count++) {
HtmlColumn column = new HtmlColumn();
HtmlOutputText header = new HtmlOutputText();
header.setValue(dynamicHeaders[count]);
column.setHeader(header);
if(dynamicHeaders[count].equals("Delete")){
HtmlCommandButton commandButton=new HtmlCommandButton();
commandButton.setValue("Delete"+count);
commandButton.setActionExpression(createActionExpression("#{relationBean.deleteRow}", String.class));
column.getChildren().add(commandButton);
}else{
HtmlInputText input=new HtmlInputText();
//List<String[]> ls = new ArrayList<String[]>();
input.setValueExpression("value",createValueExpression("#{dynamicItem}", String.class));
column.getChildren().add(input);
}
dynamicDataTable.getChildren().add(column);
}
dynamicDataTableGroup = new HtmlPanelGroup();
dynamicDataTableGroup.getChildren().add(dynamicDataTable);
debugLogger.debug("populateDynamicDataTable:Exit");
}
public HtmlPanelGroup getDynamicDataTableGroup() throws Exception {
// This will be called once in the first RESTORE VIEW phase.
if (dynamicDataTableGroup == null) {
loadDynamicList(); // Preload dynamic list.
populateDynamicDataTable(); // Populate editable datatable.
}
return dynamicDataTableGroup;
}
public List<String> getDynamicList() {
return dynamicList;
}
public void setDynamicList(List<String> dynamicList) {
this.dynamicList = dynamicList;
}
public void setDynamicDataTableGroup(HtmlPanelGroup dynamicDataTableGroup) {
this.dynamicDataTableGroup = dynamicDataTableGroup;
}
public ValueExpression createValueExpression(String valueExpression, Class<?> valueType) {
FacesContext facesContext = FacesContext.getCurrentInstance();
return facesContext.getApplication().getExpressionFactory().createValueExpression(
facesContext.getELContext(), valueExpression, valueType);
}
public MethodExpression createActionExpression(String actionExpression, Class<?> returnType) {
FacesContext facesContext = FacesContext.getCurrentInstance();
return facesContext.getApplication().getExpressionFactory().createMethodExpression(
facesContext.getELContext(), actionExpression, returnType, new Class[0]);
}
private MethodExpression createMethodExpression(String valueExpression, Class<?> valueType, Class<?>[] expectedParamTypes) {
FacesContext facesContext = FacesContext.getCurrentInstance();
return facesContext.getApplication().getExpressionFactory().createMethodExpression(
facesContext.getELContext(), valueExpression, valueType, expectedParamTypes);
}
I've the following code in my edit user screen
<h:selectManyCheckbox id="selectedGroups" value="#{usersController.selectedGroups}">
<f:selectItems value="#{usersController.groupsList}" var="item" itemLabel="#{item.groupname}" itemValue="#{item.groupid}" />
</h:selectManyCheckbox>
I've user groups list with all the groups in it, and I've selectedGroups list with the groups that are enabled for the user. But, on the edit screen, they are not showing selected by default. What am I missing? Is this not the right way to bind selected many checkboxes?
An item value of the groupsList will be preselected only when equals() method has returned true for at least one item in the selectedGroups.
Assuming that groupid is a Long, then the selectedGroups should return a List<Long> containing the values to be preselected.
Following code will work perfectly for request scope bean...
xhml code....
<h:selectManyCheckbox binding="#{Page1.chk_1}">
<f:selectItem binding="#{Page1.chk_1_options}"/>
</h:selectManyCheckbox>
Java code....
HtmlSelectManyCheckbox chk_1 = new HtmlSelectManyCheckbox();
UISelectItems chk_1_options = new UISelectItems();
public HtmlSelectManyCheckbox getChk_1() {
return chk_1;
}
public void setChk_1(HtmlSelectManyCheckbox chk_1) {
this.chk_1 = chk_1;
}
public UISelectItems getChk_1_options() {
if (chk_1_options.getValue() == null) {
List<SelectItem> lst_chk_options = new ArrayList<SelectItem>();
lst_chk_options.add(new SelectItem(1, "Label1"));
lst_chk_options.add(new SelectItem(2, "Label2"));
chk_1_options.setValue(lst_chk_options);
}
return chk_1_options;
}
public void setChk_1_options(UISelectItems chk_1_options) {
this.chk_1_options = chk_1_options;
}
If you want for session scope then reply, because binding elements in session scope giving problems in some cases...
I have tried working with the grouped selections with something like this :
<h:selectOneMenu value="#{selectionLabBean.oneSelectMenuGroup}"
id="SelectOneMenuGroup" >
<f:selectItems value="#{selectionLabBean.heroGroupList}" />
</h:selectOneMenu>
<p:message for="SelectOneMenuGroup" />
where the heroGroupList is something like this :
SelectItem[] heroArr = new SelectItem[] {
new SelectItem("Paladin"),
...
};
heroListWithGrouping.add(
new SelectItemGroup("Human",
"A collection of human race Heroes",
false,
heroArr
)
);
.....
And i'm left wondering if i can do this kind of grouping with POJOs instead of SelectItem objects ?
If i couldnt achieve this, i think i have to somehow convert my domain objects or my query results into arrays of SelectItem to make it work.
Any ideas ?
That's indeed not possible when you want to use SelectItemGroup. You need to convert from collection of POJO's to List<SelectItem> in a double for-loop during bean's (post)construction.
#PostConstruct
public void init() {
List<HeroRace> heroRaces = getItSomehowFromDatabase();
this.heroGroupList = new ArrayList<SelectItem>();
for (HeroRace heroRace : heroRaces) {
SelectItemGroup group = new SelectItemGroup(heroRace.getName()); // Human, etc
List<SelectItem> heroes = new ArrayList<SelectItem>();
for (Hero hero : heroRace.getHeroes()) {
heroes.add(new SelectItem(hero.getName()); // Paladin, etc
}
group.setSelectItems(heroes.toArray(new SelectItem[heroes.size()]));
this.heroGroupList.add(group);
}
}
You could also use Hero as item value
heroes.add(new SelectItem(hero, hero.getName()); // Paladin, etc
so that you can bind #{selectionLabBean.oneSelectMenuGroup} to a Hero type instead of String. But then you need to supply a Converter. That part is already answered by Amorfis.
Yes, you can return List or array of POJOs instead of SelectItems. You'll need converter for this to work, but it's not a big deal. So, converter first:
#FacesConverter(forClass=Hero.class)
public class HeroConverter implements Converter {
#Override
public Object getAsObject(FacesContext context, UIComponent component, String value) {
return new Hero(value);
}
#Override
public String getAsString(FacesContext context, UIComponent component, Object value) {
return ((Hero)value).getName();
}
}
Now if you return list of Heroes to <f:selectItems>, you have options in HTML where label is Hero.toString(), and value is returned from HeroConverter.getAsString().
One more thing. If you submit some value for this selection, JSF converts it to object and checks (by equals() method) if this object was in list of objects for selection. So in case above, you'll need to override equals() in Hero to check if names are equal. Another solution is not to create new instance in getAsObject, but to keep somewhere list of available Heroes and return this list to <f:selectionItems> and return object from this list in getAsObject().