PrimeFaces DataTable Enum filtering - jsf-2

I am using PF 3.4 and trying to filter a column which has enum value. I am using jsf 2.0's enum converter. But filtering does not work for me here is the sample code :
<p:dataTable id="projeTeklifiListesiDataTable"
value="#{controller.model.projeTeklifiListesi}"
var="proje" paginator="true" rowKey="#{proje.id}"
rows="50"
selection="#{controller.model.secilenProjeTeklifi}"
selectionMode="single" styleClass="defaultList">
<p:column headerText="#{etiket['pfdy_hzrl_vt013']}"
filterBy="#{projeTeklifi.projeTeklifiDurumu}"
filterOptions="#{controller.model.projeTeklifiDurumuListesi}"
sortBy="#{projeTeklifi.projeTeklifiDurumu.toString()}">
#{proje.projeTeklifiDurumu.toString()}
</p:column>
<p:column headerText="#{etiket['pfdy_dkd_vt010']}" filterBy="#{projeTeklifi.basariDurumu}"
filterOptions="#{controller.model.basariDurumuListesi}"
sortBy="#{projeTeklifi.basariDurumu.toString()}">
#{proje.basariDurumu.toString()}
</p:column>
</p:dataTable>
this are the lists for the filter options
public SelectItem[] getProjeTeklifiDurumuListesi()
{
final ProjeTeklifiDurumu[] durumListesi = ProjeTeklifiDurumu.values();
final SelectItem[] projeTeklifiDurumListesi = new SelectItem[durumListesi.length+1];
projeTeklifiDurumListesi[0] = new SelectItem("", "Seçiniz");
for(int i =0;i<durumListesi.length;i++)
{
final SelectItem select = new SelectItem(ProjeTeklifiDurumu.valueOf(durumListesi[i].name()),durumListesi[i].toString());
projeTeklifiDurumListesi[i+1]=select;
}
return projeTeklifiDurumListesi;
}
public SelectItem[] getBasariDurumuListesi()
{
final BasariDurumu[] durumListesi = BasariDurumu.values();
final SelectItem[] projeTeklifiDurumListesi = new SelectItem[durumListesi.length+1];
projeTeklifiDurumListesi[0] = new SelectItem("", "Seçiniz");
for(int i =0;i<durumListesi.length;i++)
{
final SelectItem select = new SelectItem(durumListesi[i],durumListesi[i].toString());
projeTeklifiDurumListesi[i+1]=select;
}
return projeTeklifiDurumListesi;
}
I have tried with different value bindings for SelectedItem class bind the enum itself, bind the name of the enum, called toString() method but it does not filter. I can see that my value binding of the filter option is posted correctly but does not filters the data.
Any clue would be helpful.
Thank you.

Look at org.primefaces.component.datatable.DataHelper
void decodeFilters(FacesContext context, DataTable table) {
...
String columnValue = String.valueOf(column.getValueExpression("filterBy").getValue(context.getELContext()));
...
}
They are decoding the value as is, so no converter will be used i think.

Related

JSF SelectOneMenu value cannot be user defined type? [duplicate]

I am creating a web application, where you have to read a list of objects / entities from a DB and populate it in a JSF <h:selectOneMenu>. I am unable to code this. Can someone show me how to do it?
I know how to get a List<User> from the DB. What I need to know is, how to populate this list in a <h:selectOneMenu>.
<h:selectOneMenu value="#{bean.name}">
...?
</h:selectOneMenu>
Based on your question history, you're using JSF 2.x. So, here's a JSF 2.x targeted answer. In JSF 1.x you would be forced to wrap item values/labels in ugly SelectItem instances. This is fortunately not needed anymore in JSF 2.x.
Basic example
To answer your question directly, just use <f:selectItems> whose value points to a List<T> property which you preserve from the DB during bean's (post)construction. Here's a basic kickoff example assuming that T actually represents a String.
<h:selectOneMenu value="#{bean.name}">
<f:selectItems value="#{bean.names}" />
</h:selectOneMenu>
with
#ManagedBean
#RequestScoped
public class Bean {
private String name;
private List<String> names;
#EJB
private NameService nameService;
#PostConstruct
public void init() {
names = nameService.list();
}
// ... (getters, setters, etc)
}
Simple as that. Actually, the T's toString() will be used to represent both the dropdown item label and value. So, when you're instead of List<String> using a list of complex objects like List<SomeEntity> and you haven't overridden the class' toString() method, then you would see com.example.SomeEntity#hashcode as item values. See next section how to solve it properly.
Also note that the bean for <f:selectItems> value does not necessarily need to be the same bean as the bean for <h:selectOneMenu> value. This is useful whenever the values are actually applicationwide constants which you just have to load only once during application's startup. You could then just make it a property of an application scoped bean.
<h:selectOneMenu value="#{bean.name}">
<f:selectItems value="#{data.names}" />
</h:selectOneMenu>
Complex objects as available items
Whenever T concerns a complex object (a javabean), such as User which has a String property of name, then you could use the var attribute to get hold of the iteration variable which you in turn can use in itemValue and/or itemLabel attribtues (if you omit the itemLabel, then the label becomes the same as the value).
Example #1:
<h:selectOneMenu value="#{bean.userName}">
<f:selectItems value="#{bean.users}" var="user" itemValue="#{user.name}" />
</h:selectOneMenu>
with
private String userName;
private List<User> users;
#EJB
private UserService userService;
#PostConstruct
public void init() {
users = userService.list();
}
// ... (getters, setters, etc)
Or when it has a Long property id which you would rather like to set as item value:
Example #2:
<h:selectOneMenu value="#{bean.userId}">
<f:selectItems value="#{bean.users}" var="user" itemValue="#{user.id}" itemLabel="#{user.name}" />
</h:selectOneMenu>
with
private Long userId;
private List<User> users;
// ... (the same as in previous bean example)
Complex object as selected item
Whenever you would like to set it to a T property in the bean as well and T represents an User, then you would need to bake a custom Converter which converts between User and an unique string representation (which can be the id property). Do note that the itemValue must represent the complex object itself, exactly the type which needs to be set as selection component's value.
<h:selectOneMenu value="#{bean.user}" converter="#{userConverter}">
<f:selectItems value="#{bean.users}" var="user" itemValue="#{user}" itemLabel="#{user.name}" />
</h:selectOneMenu>
with
private User user;
private List<User> users;
// ... (the same as in previous bean example)
and
#ManagedBean
#RequestScoped
public class UserConverter implements Converter {
#EJB
private UserService userService;
#Override
public Object getAsObject(FacesContext context, UIComponent component, String submittedValue) {
if (submittedValue == null || submittedValue.isEmpty()) {
return null;
}
try {
return userService.find(Long.valueOf(submittedValue));
} catch (NumberFormatException e) {
throw new ConverterException(new FacesMessage(String.format("%s is not a valid User ID", submittedValue)), e);
}
}
#Override
public String getAsString(FacesContext context, UIComponent component, Object modelValue) {
if (modelValue == null) {
return "";
}
if (modelValue instanceof User) {
return String.valueOf(((User) modelValue).getId());
} else {
throw new ConverterException(new FacesMessage(String.format("%s is not a valid User", modelValue)), e);
}
}
}
(please note that the Converter is a bit hacky in order to be able to inject an #EJB in a JSF converter; normally one would have annotated it as #FacesConverter(forClass=User.class), but that unfortunately doesn't allow #EJB injections)
Don't forget to make sure that the complex object class has equals() and hashCode() properly implemented, otherwise JSF will during render fail to show preselected item(s), and you'll on submit face Validation Error: Value is not valid.
public class User {
private Long id;
#Override
public boolean equals(Object other) {
return (other != null && getClass() == other.getClass() && id != null)
? id.equals(((User) other).id)
: (other == this);
}
#Override
public int hashCode() {
return (id != null)
? (getClass().hashCode() + id.hashCode())
: super.hashCode();
}
}
Complex objects with a generic converter
Head to this answer: Implement converters for entities with Java Generics.
Complex objects without a custom converter
The JSF utility library OmniFaces offers a special converter out the box which allows you to use complex objects in <h:selectOneMenu> without the need to create a custom converter. The SelectItemsConverter will simply do the conversion based on readily available items in <f:selectItem(s)>.
<h:selectOneMenu value="#{bean.user}" converter="omnifaces.SelectItemsConverter">
<f:selectItems value="#{bean.users}" var="user" itemValue="#{user}" itemLabel="#{user.name}" />
</h:selectOneMenu>
See also:
Our <h:selectOneMenu> wiki page
View-Page
<h:selectOneMenu id="selectOneCB" value="#{page.selectedName}">
<f:selectItems value="#{page.names}"/>
</h:selectOneMenu>
Backing-Bean
List<SelectItem> names = new ArrayList<SelectItem>();
//-- Populate list from database
names.add(new SelectItem(valueObject,"label"));
//-- setter/getter accessor methods for list
To display particular selected record, it must be one of the values in the list.
Roll-your-own generic converter for complex objects as selected item
The Balusc gives a very useful overview answer on this subject. But there is one alternative he does not present: The Roll-your-own generic converter that handles complex objects as the selected item. This is very complex to do if you want to handle all cases, but pretty simple for simple cases.
The code below contains an example of such a converter. It works in the same spirit as the OmniFaces SelectItemsConverter as it looks through the children of a component for UISelectItem(s) containing objects. The difference is that it only handles bindings to either simple collections of entity objects, or to strings. It does not handle item groups, collections of SelectItems, arrays and probably a lot of other things.
The entities that the component binds to must implement the IdObject interface. (This could be solved in other way, such as using toString.)
Note that the entities must implement equals in such a way that two entities with the same ID compares equal.
The only thing that you need to do to use it is to specify it as converter on the select component, bind to an entity property and a list of possible entities:
<h:selectOneMenu value="#{bean.user}" converter="selectListConverter">
<f:selectItem itemValue="unselected" itemLabel="Select user..."/>
<f:selectItem itemValue="empty" itemLabel="No user"/>
<f:selectItems value="#{bean.users}" var="user" itemValue="#{user}" itemLabel="#{user.name}" />
</h:selectOneMenu>
Converter:
/**
* A converter for select components (those that have select items as children).
*
* It convertes the selected value string into one of its element entities, thus allowing
* binding to complex objects.
*
* It only handles simple uses of select components, in which the value is a simple list of
* entities. No ItemGroups, arrays or other kinds of values.
*
* Items it binds to can be strings or implementations of the {#link IdObject} interface.
*/
#FacesConverter("selectListConverter")
public class SelectListConverter implements Converter {
public static interface IdObject {
public String getDisplayId();
}
#Override
public Object getAsObject(FacesContext context, UIComponent component, String value) {
if (value == null || value.isEmpty()) {
return null;
}
return component.getChildren().stream()
.flatMap(child -> getEntriesOfItem(child))
.filter(o -> value.equals(o instanceof IdObject ? ((IdObject) o).getDisplayId() : o))
.findAny().orElse(null);
}
/**
* Gets the values stored in a {#link UISelectItem} or a {#link UISelectItems}.
* For other components returns an empty stream.
*/
private Stream<?> getEntriesOfItem(UIComponent child) {
if (child instanceof UISelectItem) {
UISelectItem item = (UISelectItem) child;
if (!item.isNoSelectionOption()) {
return Stream.of(item.getValue());
}
} else if (child instanceof UISelectItems) {
Object value = ((UISelectItems) child).getValue();
if (value instanceof Collection) {
return ((Collection<?>) value).stream();
} else {
throw new IllegalStateException("Unsupported value of UISelectItems: " + value);
}
}
return Stream.empty();
}
#Override
public String getAsString(FacesContext context, UIComponent component, Object value) {
if (value == null) return null;
if (value instanceof String) return (String) value;
if (value instanceof IdObject) return ((IdObject) value).getDisplayId();
throw new IllegalArgumentException("Unexpected value type");
}
}
I'm doing it like this:
Models are ViewScoped
converter:
#Named
#ViewScoped
public class ViewScopedFacesConverter implements Converter, Serializable
{
private static final long serialVersionUID = 1L;
private Map<String, Object> converterMap;
#PostConstruct
void postConstruct(){
converterMap = new HashMap<>();
}
#Override
public String getAsString(FacesContext context, UIComponent component, Object object) {
String selectItemValue = String.valueOf( object.hashCode() );
converterMap.put( selectItemValue, object );
return selectItemValue;
}
#Override
public Object getAsObject(FacesContext context, UIComponent component, String selectItemValue){
return converterMap.get(selectItemValue);
}
}
and bind to component with:
<f:converter binding="#{viewScopedFacesConverter}" />
If you will use entity id rather than hashCode you can hit a collision- if you have few lists on one page for different entities (classes) with the same id
Call me lazy but coding a Converter seems like a lot of unnecessary work. I'm using Primefaces and, not having used a plain vanilla JSF2 listbox or dropdown menu before, I just assumed (being lazy) that the widget could handle complex objects, i.e. pass the selected object as is to its corresponding getter/setter like so many other widgets do. I was disappointed to find (after hours of head scratching) that this capability does not exist for this widget type without a Converter. In fact if you supply a setter for the complex object rather than for a String, it fails silently (simply doesn't call the setter, no Exception, no JS error), and I spent a ton of time going through BalusC's excellent troubleshooting tool to find the cause, to no avail since none of those suggestions applied. My conclusion: listbox/menu widget needs adapting that other JSF2 widgets do not. This seems misleading and prone to leading the uninformed developer like myself down a rabbit hole.
In the end I resisted coding a Converter and found through trial and error that if you set the widget value to a complex object, e.g.:
<p:selectOneListbox id="adminEvents" value="#{testBean.selectedEvent}">
... when the user selects an item, the widget can call a String setter for that object, e.g. setSelectedThing(String thingString) {...}, and the String passed is a JSON String representing the Thing object. I can parse it to determine which object was selected. This feels a little like a hack, but less of a hack than a Converter.

Reordering p: p:dataTable rows containing inputs

There is p:dataTable with p:inputText in a column:
<h:form id="form">
<p:dataTable id="dataTable" value="#{rowReorder.dataList}"
var="row" draggableRows="true" rowKey="#{row.id}">
<p:ajax event="rowReorder" listener="#{rowReorder.reorder}" update="dataTable"/>
<p:column>
<f:facet name="header">
<p:commandButton value="Add" actionListener="#{rowReorder.addData}"
update="dataTable" process="dataTable"/>
</f:facet>
<p:outputLabel value="#{row.id}"/>
</p:column>
<p:column>
<p:inputText value="#{row.name}"/>
</p:column>
</p:dataTable>
</h:form>
Backing bean:
import org.omnifaces.cdi.ViewScoped;
import org.primefaces.event.ReorderEvent;
import javax.inject.Named;
import java.io.Serializable;
import java.util.LinkedList;
import java.util.List;
#Named("rowReorder")
#ViewScoped
public class RowReorder implements Serializable {
private List<Data> dataList = new LinkedList<>();
public void addData() {
Data data = new Data();
data.setId(dataList.size() + 1);
data.setName("Data " + data.getId());
dataList.add(data);
}
public void reorder(ReorderEvent event) {
}
/**
* Getters, Setters
*/
public List<Data> getDataList() {
return dataList;
}
}
Data class:
public class Data implements Serializable {
private Integer id;
private String name;
/**
* Getters, Setters
*/
}
Sample datatable before reordering:
--------------
|id | name |
--------------
| 1 | Data 1 |
| 2 | Data 2 |
| 3 | Data 3 |
| 4 | Data 4 |
--------------
and after reordering (moving 1-st row to 3-rd):
--------------
|id | name |
--------------
| 2 | Data 1 |
| 3 | Data 2 |
| 1 | Data 3 |
| 4 | Data 4 |
--------------
I understand that it is happening 'cause of setting data from p:inputText's at UPDATE_MODEL phase. I tried to prevent processing of input fields by specifying process="#none" in p:ajax component, but it doesn't work. Have any idea how to make draggableRows and p:inputText friends?
First solution
I found a solution!
It does not processing inputs (and actually does not submit it at all) with attributes process="#none" partialSubmit="true"
So complete p:ajax component looks like
<p:ajax event="rowReorder" listener="#{rowReorder.reorder}" update="dataTable" process="#none" partialSubmit="true"/>
Second solution (if submitted data is needed)
Theory:
Lets check out what is happening on dragging row?
We have ajax request forcing process="form:dataTable". On APPLY_REQUEST_VALUES phase DataTableRenderer tries to invoke decode of DraggableRowsFeature which, in turn, rotating list elements (list that specified as dataTable's value attribute). So on UPDATE_MODEL_VALUES phase we have a rotated list, and input components, which wants to submit their values to name fields of Data objects. But request parameters still contains old row indexes in input ids: they are form:dataTable:1:name = Data 2, form:dataTable:2:name = Data 3, form:dataTable:0:name = Data 1 (i added 3 rows, and moved first row to last). So here we getting what we got. In this way if we need data to be submitted on right places we have to prevent our list rotating before UPDATE_MODEL_VALUES is done,
and perform this rotation later on INVOKE_APPLICATION phase, and render dataTable on that ajax request:
In DraggableRowsFeature.decode() we can see that Collections.rotate() is calling only when value is instance of List.
if (value instanceof List) {
List list = (List) value;
if(toIndex >= fromIndex) {
Collections.rotate(list.subList(fromIndex, toIndex + 1), -1);
}
else {
Collections.rotate(list.subList(toIndex, fromIndex + 1), 1);
}
}
else {
LOGGER.info("Row reordering is only available for list backed datatables, use rowReorder ajax behavior with listener for manual handling of model update.");
}
Also there is DraggableRowsFeature.shouldDecode() method.
public boolean shouldDecode(FacesContext context, DataTable table) {
return context.getExternalContext().getRequestParameterMap().containsKey(table.getClientId(context) + "_rowreorder");
}
So here we have 2 possibilities to prevent datasource rotating:
Don't use List as dataTable value
Create own org.primefaces.component.datatable.feature.DraggableRowsFeature returning false in shouldDecode() method.
Practice:
I modified bean file like this:
#Named("rowReorder")
#ViewScoped
public class RowReorder implements Serializable {
private static final Logger log = LoggerFactory.getLogger(RowReorder.class);
private Set<Data> dataList = new LinkedHashSet<>();
public void addData() {
Data data = new Data();
data.setId(dataList.size() + 1);
data.setName("Data " + data.getId());
dataList.add(data);
log.warn("{} {}", Integer.toHexString(data.hashCode()), data.getId());
}
public void removeData(Data data) {
dataList.remove(data);
}
public void reorder(ReorderEvent event) {
List<Data> list = new LinkedList<>(dataList);
int fromIndex = event.getFromIndex();
int toIndex = event.getToIndex();
if(toIndex >= fromIndex) {
Collections.rotate(list.subList(fromIndex, toIndex + 1), -1);
}
else {
Collections.rotate(list.subList(toIndex, fromIndex + 1), 1);
}
dataList.clear();
dataList.addAll(list);
}
/**
* Getters, Setters
*/
public Set<Data> getDataList() {
return dataList;
}
}
And now it firstly submitting values to model and rotating list on INVOKE_APPLICATION phase.
The secret is the attribute rowStatePreserved of your datatable, add it:
rowStatePreserved="true"
<p:dataTable id="dataTable" value="#{rowReorder.dataList}"
var="row" draggableRows="true" rowKey="#{row.id}"
rowStatePreserved="true">
And keep this code as like:
<p:ajax event="rowReorder" listener="#{rowReorder.reorder}" update="dataTable" process="#this"/>
In my case i was used a combobox into datatable column, and after i added this atrribute the value do not change more than one row to another when i used draggableRows function.
I wait help you.
Other simple solution is to disable the inputs on the start of rowReorder:
<p:ajax event="rowReorder"
onstart="$(':input', PrimeFaces.escapeClientId('#{component.clientId}')).prop('disabled',true)"
update="#this"/>
Note that #{component.clientId} will return the client ID of the data table.
To avoid loosing data, you can Ajaxify the inputs:
<p:column headerText="#{msg.value}">
<p:inputText value="#{item.value}">
<p:ajax/>
</p:inputText>
</p:column>

add only one value to a picklist target (PrimeFaces)

I have a search function which searches the system. After performing search I am updating the search result in to pick List. Each Time I search I will update pickList source. Now My problem is I want to validate in such a way that if more than 1 value is added to the pick list target It should give me error message and also check about the duplicate entries in picklist target.
Below is the code of picklist in xhmtl
<p:pickList id="primaryContactBean" value="#{addSystemManagedBean.primaryContactList}" var="contact" itemLabel="#{contact.firstName}" itemValue="#{contact}" converter="contactConverter" >
<p:ajax event="transfer" listener="#{addSystemManagedBean.onTransfer}" process="#this" update="sysMsg3" />
<p:column>
#{contact.firstName} #{contact.lastName}
</p:column>
</p:pickList>
and onTransfer the following method gets called
public int primaryOwnerCount = 0;
public void onTransfer(TransferEvent event) {
PickList picklist = (PickList) event.getComponent();
for(Object item : event.getItems()) {
ContactBean cb = (ContactBean)item;
if(picklist.getId().equals("primaryContactBean")){
if(event.isAdd())
primaryOwnerCount++;
if(primaryOwnerCount>1){
FacesContext.getCurrentInstance().addMessage(null, new FacesMessage(FacesMessage.SEVERITY_ERROR,"Only one primary owner can exist!!","If you want to add owner, please remove from the list and then proceed.."));
for (Iterator iterator = primaryContactList.getTarget().iterator(); iterator
.hasNext();) {
ContactBean type = (ContactBean) iterator.next();
System.out.println("target:"+type.getFirstName());
}
}
else
primaryContactTarget.add(cb);
}

Binding primefaces dataTable with org.primefaces.component.datatable.DataTable;

I have a question regarding primefaces datatable component. I want to bind a DataTable variable to the p:dataTable so that I would be able to manipulate the first, rows, rowsPerPageTemplate, etc. programmatically from the backing bean. But I'm stuck and keep getting java.lang.String cannot be cast to javax.faces.component.UIComponent.
Here's my p:dataTable declaration.
<p:dataTable id="dtProductCategoryList" value="#{saProductCategoryController.saproductcategories}" rowsPerPageTemplate="#{appConfig.rowsPerPageTemplate}"
paginatorTemplate="{RowsPerPageDropdown} {FirstPageLink} {PreviousPageLink} {CurrentPageReport} {NextPageLink} {LastPageLink}"
currentPageReportTemplate="{currentPage} #{bundle.DataTablePageSeparator} {totalPages}"
paginatorAlwaysVisible="false" var="item" paginator="true" rows="#{appConfig.rowsPerPageDefault}"
binding="saProductCategoryController.dtProductCategory">
And here's my ViewScoped backing bean.
private DataTable dtProductCategory;
/** Creates a new instance of saProductCategoryController */
public SaProductCategoryController() {
}
#PostConstruct
public void Init() {
try {
dtProductCategory = new DataTable();
//dtProductCategory.
saproductcategories = saProductCategoryFacade.selectAll();
LogController.log.info("Creating postconstruct for saProductCategoryController");
} catch (Exception ex) {
LogController.log.fatal(ex.toString());
}
}
What could be the problem? It seems that the DataTable variable is mistaken for a String?
Appreciate all your help. Thanks.
java.lang.String cannot be cast to javax.faces.component.UIComponent.
The binding attribute must refer an UIComponent, not a plain vanilla String. And indeed, you forgot the #{} around the attribute value which would make it to be treated as a plain vanilla String.
Fix it accordingly:
binding="#{saProductCategoryController.dtProductCategory}"
Replace
binding="saProductCategoryController.dtProductCategory"
with
binding="#{saProductCategoryController.dtProductCategory}"

How to retrieve value from linkedhashmap using iterators in Struts2......?

I have function which return LinkedHashMap in Struts2 and i just came to know that we cannot use for loop in struts2 instead we have to use Iterators, and am new to struts
can any on help me to retrieve value from linkedhashmap using iterators, below is how values are lined up in hashmap:
LinkedHashMap<String, ArrayList<String>> topSuppliers = new LinkedHashMap<String, ArrayList<String>>();
while(resultset.next()){
ArrayList<String> innerList = new ArrayList<String>();
String manufId = resultset.getString("manufacturer_id");
String manufLogo = resultset.getString("SUPPLIER_LOGO_IMAGE");
String manufName = resultset.getString("MANUFACTURER_NAME");
String manufURL = resultset.getString("MANUFACTURER_URL");
innerList.add(manufId);
innerList.add(manufLogo);
innerList.add(manufName);
innerList.add(manufURL);
topSuppliers.put(manufName,innerList);
}
return topSuppliers;
And i want to display them in a set of 4 manufacturers:
Set1: 1,2,3,4
Set2: 5,6,7,8
Set3: 9,10,11,12
etc......
Thank you........
You should iterate over List of Map instead of Map of List
Example :
#Getter
private List<Map> listOfMap = Lists.newArrayList();
public String execute() {
while (resultset.next()) {
final Map<String, String> map = Maps.newHashMap();
map.put("manufId", resultset.getString("manufacturer_id"));
map.put("manufLogo", resultset.getString("SUPPLIER_LOGO_IMAGE"));
map.put("manufName", resultset.getString("MANUFACTURER_NAME"));
map.put("manufURL", resultset.getString("MANUFACTURER_URL"));
listOfMap.add(map);
}
return SUCCESS;
}
<s:iterator value="listOfMap">
${manufId}
${manufLogo}
${manufName}
${manufURL}
</s:iterator>
The listOfMap also can use as a dataSource for Struts2 JasperReports Plugin.
You can use s:iterator over a map.
<s:iterator value="topSuppliers">
<s:property value="key" />: <s:iterator value="value" status="status"><s:property /><s:if test="!#status.last">,</s:if></s:iterator>
</s:iterator>
This iterates over the map using Map.Entry and then iterates over your value list using another iterator and iterator status to add "," unless it's the last entry.

Resources