Display DataTable with unknown numbers of column - jsf

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

Related

Dropdown list is not populating in JSF

I am working on Managedbeans and JSF. As shown below that my ManagedBean contains all the requirements that are required for the JSF to get the value. I have initialised my dropdown list as below. In selectOneMenu, I have chosen the country as a string where it will store the value selected by the dropdown list and the dropdown list will bring up the list that I declared in the Beans.
Unfortunately, it is not happening like that. Every time dropdown renders it gives me an empty value. I have spent days on it but cannot figure out the exact solution to it. I have cleaned my server, build workspace and also change servers but nothing is working.
** ManagedBean_list **
private List<String> listCountry;
private String country;
public void tada(){
listCountry=Arrays.asList("India", "pakisatan","America");
}
public List<String> getListCountry() {
return listCountry;
}
public void setListCountry(List<String> listCountry) {
this.listCountry = listCountry;
}
public String getCountry() {
return country;
}
public void setCountry(String country) {
this.country = country;
}
JSF
<p:selectOneMenu id="country" value="#{loginBeans.country}">
<f:selectItems value="#{loginBeans.listCountry}" />
</p:selectOneMenu>
Your help is appreciated. Empty dropdown list image
enter image description here
Which bean annotation are you using? You say "Managedbeans", but the source you posted does not show the entire bean, or does it? Check to make sure you are not mixing old style JSF managed bean annotations with CDI annotations.
The issue is that on initialization, the list is not being called up. I resolved it by including the list function inside the constructor of managed beans class. so that when the constructor fired up. It also generates the dropdown list.
Either convert your listCountry to a
private Map<String, String> listCountry = new HashMap<>();
listCountry.put("India", "India");
listCountry.put("Pakistan", "Pakistan");
listCountry.put("America", "America");
or
private List<SelectItem> listCountry = new ArrayList<>();
listCountry.add(new SelectItem("India", "India"));
listCountry.add(new SelectItem("Pakistan", "Pakistan"));
listCountry.add(new SelectItem("America","America"));

How bind primefaces datatable from managedbean

How bind primefaces datatable in managedbean? How put the data, and how put columns?
My bean class:
public class BeanTest implements Serializable{
private String name;
private String email;
private int age;
//getters and setters
}
My managed bean:
public class TestTable implements Serializable{
private DataTable tabela;
private List<BeanTest> lista;
#PostConstruct
public void init() {
int age= 18;
this.lista = new ArrayList<>();
this.lista.add(new BeanTest("name1", "email1", age));
this.lista.add(new BeanTest("name2", "email2", age++));
this.lista.add(new BeanTest("name3", "email3", age++));
this.tabela = new DataTable();
Column column1 = new Column();
column1.setHeaderText("Nome");
Column column2 = new Column();
column2.setHeaderText("Email");
Column column3 = new Column();
column3.setHeaderText("Idade");
this.getTabela().getChildren().add(column1);
this.getTabela().getChildren().add(column2);
this.getTabela().getChildren().add(column3);
this.getTabela().setValue(this.lista);
}
}
JSF page:
<p:dataTable id="datalist" binding="#{testeTabela.tabela}">
</p:dataTable>
This display the table with three columns (correct, number and headers) and three rows(correct numbers), but there's no data in my rows. Empty table only with borders cells.
What's happening? How could i bind columns and data?
In general, a JSF component has 3 parts: a tag, a component class and a renderer.
The tag is responsible for the component configuration. It will instantiate your component and set the appropriate attributes, listeners and facets. Once configured, the component will be put on the component tree.
Using your example, the page code will look similar to this:
<p:dataTable id="dataTable" var="item" value="#{bean.list}">
<p:column headerText="Name">#{item.name}</p:column>
<p:column headerText="Email">#{item.email}</p:column>
<p:column headerText="Age">#{item.age}</p:column>
</p:dataTable>
It's easier to do that way. However, if you want to do in code, you need to add some components inside the columns to make it work.
First, set the var attribute on the datatable. The component (datatable) will iterate over your items and bind the current item to that name, so the child components can use an expression to dynamically get that value.
this.getTabela().setVar("item");
Second, add a child UIOutput to the column and add an expression to the its value property. For the name column, it would be something like this:
FacesContext context = FacesContext.getCurrentInstance();
//Creates the output and sets the value to an expression language
UIOutput output1 = new UIOutput();
output1.setValueExpression("value",context.getApplication().getExpressionFactory().createValueExpression(context.getELContext(),"#{item.name}", String.class));
//Add the output to the column
column1.getChildren().add(output1);
For the other columns, it's the same idea, except for the type of the third UIOutput's value:
...createValueExpression(context.getELContext(),"#{item.age}", Integer.class));
As you can probably see, this can be hard to maintain.
Using tags is cleaner and easier to read.

How do I pass objects from the request map into a method expression after rendering a component?

I'm writing a custom datatable component, and I'm trying to allow other developers to use the var of the current iteration to be passed to a method expression.
Here's an example:
<my:dataTable value="#{contactController.viewEntities}" var="contact">
<p:column headerText="Whatever">
<p:commandButton value="Edit"
ajax="false"
actionListener="#{contactController.setSelectedContact(contact)}"
action="contact"/>
</p:column>
</my:dataTable>
As you can see, I'm trying to pass the "contact" var into a setter method for later use. That var is stored in the request map during rendering, like so:
...
Map<String, Object> requestMap = context.getExternalContext()
.getRequestMap();
for (Object obj : collection) {
requestMap.put(table.getVar(), obj);
for (UIColumn column : columns) {
column.encodeAll(context);
}
requestMap.remove(table.getVar());
}
...
However, when I run the project, the only value passed to the controller is null.
The question becomes, how do I make that object available for each row to use in expression language? I've looked at PrimeFaces'/ICEFaces' datatables, but I don't see how they differ from mine
Thanks in advance!
So, I finally solved this issue! When extending UIData, the method setRowIndex(index) becomes available. By setting this value, the clientId of each row's component will be prepended with the index number, thus making them unique per row. The trick was to set this value while iterating over the underlying collection. Expression language can now evaluate the var parameters.
int index = 0;
Map<String, Object> requestMap = context.getExternalContext()
.getRequestMap();
for (Object obj : collection) {
requestMap.put(table.getVar(), obj);
table.setRowIndex(index);
for (UIColumn column : columns) {
column.encodeAll(context);
}
requestMap.remove(table.getVar());
index ++;
}

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.

JSF 2 : selection grouping with SelectItemGroup + POJO

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().

Resources