Extend of CSVExporter throws NPE - jsf

I work with Primefaces 4-SNAPSHOT and I have some DataTables with <h:graphicImage> in them which I would like to export into different documents.
The fix that is desribed in p:dataExporter does not recognize p:cellEditor
which is supplied by BalusC works fine for the ExcelExporter, but does not work in the same way fot the CSVExporter.
I get NPE when I try to create the document.
I tried to remove the first lines of Code from the Extend-Class, because I have no CellEditor-Elements. This sort of works. It has no NPEs bus this gives me a document with the toString() of the graphicImage and the alt value.
The Extended-Class is this:
public class ExtendedCSVExporter extends CSVExporter
{
#Override
protected String exportValue(FacesContext context, UIComponent component)
{
if (component instanceof HtmlGraphicImage)
{
return (String) component.getAttributes().get("alt");
}
else
{
return super.exportValue(context, component);
}
}
}
And I call it like described in the referenced topic
public void exportPDF(DataTable table, String filename) throws IOException {
FacesContext context = FacesContext.getCurrentInstance();
Exporter exporter = new ExtendedCSVExporter();
exporter.export(context, table, filename, false, false, "UTF-8", null, null);
context.responseComplete();
}
The column of the Table looks like this:
<p:column sortBy="state">
<f:facet name="header">
<h:outputText value="State">
</f:facet>
<h:graphicImage id="stateImg" name="state.png" library="icons" value="#{bean.state} />
<p:tooltip for="stateImg" value="Allowed" showEffect="fade" hideEffect="fade" />
</p:column>
For the Excel export this works fine, and the value which is taken for the image is the value of the tooltip because the graphic image has no alt-value.
But this does not work for CSV files
What I get in my document for pageOnly is:
"javax.faces.component.html.HtmlGraphicImage#23e645d8AltText"
For the whole Table I get a NPE
Any suggestions how to fix this?

Okay I know have found a solution to my problem.
I just override the HtmlGraphicImage.
I modified the Extended-Class so the GraphicImage always returns an empty String and I fetch the value of the tooltip.
So the Extende-Class looks like this:
public class ExtendedCSVExporter extends CSVExporter
{
#Override
protected String exportValue(FacesContext context, UIComponent component)
{
if (component instanceof HtmlGraphicImage)
{
return "";
}
else
{
return super.exportValue(context, component);
}
}
}

Related

ValueChangeEvent although value has not changed

I cannot see why the question is duplicate. If I debug the code then - when the button is clicked - no new value of projectSelected is being detected. Even the hashCode is the same. The equals method of the ProjectEntity only contains the id which is the same since it comes from the database and is not changed anywhere. Null values don't exist in the selection.
There was, however, too much code to reproduce the problem. I removed unnecessary code and the problem still persists.
Original question: In the following form with 3 <p:selectOneMenu> -fields if the submit button is clicked a valueChangeEvent is fired for the projectSelector field although it hasn't changed. Why is that? Like that the actual action behind the button is never called. I would expect a valueChangeEvent to be fired only in case the project changes.
Update: Trying to find the cause I replaced the ProjectEntity with String and then it worked. So I thought it must be the equals method of ProjectEntity but that only compares the id. I debugged further and found out that the selected value is being compared with a ProjectEntity with all fields set to null which gives a false and hence a valueChangeEvent. So the question is why is there a ProjectEntity with all fields set to null? I debugged into UIInput.compareValues which has that "null"-ProjectEntity being the previous value. That is being returned by UIOuput.getLocalValue. Where does it come from?
Update2: Even when using the equals and hashCode from selectOneMenu shows after submit always the last item in the list as selected item the behaviour does not change. I created an ear file readily to be deployed to e.g. a wildfly and would appreciate any help since I am stuck on this question.
<h:form>
<p:outputLabel value="#{msgs.timeProject}"/>
<p:selectOneMenu value="#{timeBean.model.projectSelected}"
converter="projectConverter"
onchange="submit()"
valueChangeListener="#{timeBean.projectChanged}"
immediate="true"
required="true">
<f:selectItems value="#{timeBean.model.allProjects}"
var="singleProject"
itemValue="#{singleProject}"
itemLabel="#{singleProject.name}"/>
</p:selectOneMenu>
<p:commandButton value="#{msgs.send}"
action="#{timeBean.myAction}"
ajax="false"/>
<p:outputLabel value="#{timeBean.model.resultValue}"
rendered="#{not empty timeBean.model.resultValue}"/>
</h:form>
The converter
#FacesConverter(value = "projectConverter")
public class ProjectConverter implements Converter {
#Inject
private ProjectService projectService;
#Override
public Object getAsObject(final FacesContext facesContext, final UIComponent uiComponent, final String projectName) {
if (StringUtils.isEmpty(projectName)) {
return null;
}
final List<ProjectEntity> projects = projectService.findAll();
for (ProjectEntity project : projects) {
if (StringUtils.equals(projectName, project.getName())) {
return project;
}
}
return null;
}
#Override
public String getAsString(final FacesContext facesContext, final UIComponent uiComponent, final Object value) {
if (value == null) {
return null;
}
if (value instanceof ProjectEntity) {
return ((ProjectEntity) value).getName();
}
return "???projectName???";
}
}
The equals-method of the ProjectEntity
#Override
public boolean equals(final Object o) {
if (this == o)
return true;
if (o == null || getClass() != o.getClass())
return false;
final ProjectEntity that = (ProjectEntity) o;
return id != null ? id.equals(that.id) : that.id == null;
}
And the change listener inside the timeBean
public void projectChanged(final ValueChangeEvent event) {
final ProjectEntity projectSelected = (ProjectEntity) event.getNewValue();
model.setProjectSelected(projectSelected);
final FacesContext context = FacesContext.getCurrentInstance();
context.renderResponse();
}
The TimeModel
public class TimeModel {
private ProjectEntity projectSelected;
private List<ProjectEntity> allProjects;
private String resultValue;
... getters and setters ...
I'll guess, that the problem resides inside the ProjectConverter class, cause it may run into troubles to assign a valid projectService instance. Maybe you remove the injection and try to compute the value programatically in the getAsObject, getAsString methods by explicit cdi-finders.
I remember to run in a similar situation, when i was injecting in a ServletFilter.

p:selectOneMenu doesn't render custom content via p:column on List<String>

I'm trying to get a Primefaces 5.2 selectOneMenu to display images along with their filenames. This is what my xhtml currently looks like:
<h:form>
<h:panelGrid id="createPanelGrid" columns="2">
<p:outputLabel value="Service Logo:" />
<p:selectOneMenu value="#{imageBean.selectedImage}" var="l">
<f:selectItem itemLabel="Select a logo" itemValue="" />
<f:selectItems value="#{imageBean.imageList}" var="logo" itemLabel="#{logo}" itemValue="#{logo}" />
<p:column>
<p:graphicImage value="#{imageBean.imageFolder}/#{l}" style="max-width:50px;max-height:50px;" />
</p:column>
<p:column>#{l}</p:column>
</p:selectOneMenu>
</h:panelGrid>
The ManagedBean (imageBean) has
public List<String> getImageList () {
List<String> imageList = new ArrayList<String>();
File[] files = absoluteImageFolder.listFiles();
for (File file : files) {
imageList.add(file.getName());
}
return imageList;
}
and
private String selectedImage;
public String getSelectedImage() {
return selectedImage;
}
public void setSelectedImage(String selectedImage) {
this.selectedImage = selectedImage;
}
However, the images are not rendered on the webpage, just the filenames (I'd post a screenshot but I don't have enough reputation). I don't get two columns (first the image, then the filename), I just get the filename itself.
When I wrap the filename Strings into a POJO and use a converter it works - but just with Strings it doesn't.
How can I get this to work with just Strings?
This awkward behavior is confirmed by SelectOneMenuRenderer source code (line numbers match 5.2):
260 if(itemValue instanceof String) {
261 writer.startElement("td", null);
262 writer.writeAttribute("colspan", columns.size(), null);
263 writer.writeText(selectItem.getLabel(), null);
264 writer.endElement("td");
265 }
266 else {
267 for(Column column : columns) {
268 writer.startElement("td", null);
269 renderChildren(context, column);
270 writer.endElement("td");
271 }
272 }
So, if the item value is an instance of String, custom content via <p:column> is totally ignored. This does indeed not make any sense. The intuitive expectation is that the custom content is toggled by presence of var attribute and/or <p:column> children. You'd best report an issue to PrimeFaces guys to explain/improve this.
The work around, apart from providing non-String-typed item values, is to override the SelectOneMenuRenderer with a custom renderer which wraps the String in another object which happens to return exactly the same value in its toString(), such as StringBuilder. This way the renderer will be fooled that the values aren't an instance of String. Glad they didn't check for instanceof CharSequence.
public class YourSelectOneMenuRenderer extends SelectOneMenuRenderer {
#Override
protected void encodeOptionsAsTable(FacesContext context, SelectOneMenu menu, List<SelectItem> selectItems) throws IOException {
List<SelectItem> wrappedSelectItems = new ArrayList<>();
for (SelectItem selectItem : selectItems) {
Object value = selectItem.getValue();
if (value instanceof String) {
value = new StringBuilder((String) value);
}
wrappedSelectItems.add(new SelectItem(value, selectItem.getLabel()));
}
super.encodeOptionsAsTable(context, menu, wrappedSelectItems);
}
}
In order to get it to run, register it as below in faces-config.xml:
<render-kit>
<renderer>
<component-family>org.primefaces.component</component-family>
<renderer-type>org.primefaces.component.SelectOneMenuRenderer</renderer-type>
<renderer-class>com.example.YourSelectOneMenuRenderer</renderer-class>
</renderer>
</render-kit>
The reason is that Primefaces library is especting a bean in the var attribute of the selectOneMenu component, but you are giving String objects which are not beans . So the library doesn't render any column overlay at all. You will need a bean (wrapper) in the var attribute and the corresponding Converter in the converter attribute.

p:selectOneMenu validation fails

I'm using PrimeFaces SelectOneMenu as follows:
<p:selectOneMenu id="inviteAbleRoleSelect"
styleClass="dropDownSelector"
value="#{invitationManagedBean.selectedRole}">
<f:selectItems value="#{invitationManagedBean.inviteAbleRoles}"
var="role"
itemValue="#{role}"
itemLabel="#{localized[role.concat('RoleName')]}">
</f:selectItems>
</p:selectOneMenu>
And the following JavaScript called on complete:
function handleUserInvitationRequest(xhr,status,args){
if (args.validationFailed) {
jQuery('#userInviterDialog').effect("shake", {
times : 3
}, 70);
} else {
userInviterDialogVar.hide();
refreshInvitedUserList();
}
}
When I submit the form, I get an ajax POST with the correctly filled form, but when it calls the handler, the validationFailed is true, even if the element is not required. Also the changes coming in the ajax response sets the select to an empty select.
Any idea?
It was a totally lame fault...
The bean was #RequestScoped ...
At least #ViewScoped is required.
If your role is not a String or any primitive type, then you are probably getting Validation Error: Value is not valid, therefor you need to implement a Converter for your Role Entity/Object, it would be something like this.
#FacesConverter("roleConverter")
public class RoleConverter implements Converter {
#Override
public Object getAsObject(FacesContext context,
UIComponent component,
String value) {
//prepare your datasource ex. EJB .. etc
if (value.trim().equals("")) {
return null;
} else {
Role role = datasource.findById(value); //get role from database
retun role;
}
}
#Override
public String getAsString(FacesContext context,
UIComponent component,
Object value) {
if (value == null || value.equals("")) {
return "";
} else {
return String.valueOf(((Role) value).getId());
}
}
}
selectOneMenu
<p:selectOneMenu id="inviteAbleRoleSelect"
styleClass="dropDownSelector"
value="#{invitationManagedBean.selectedRole}" converter="roleConverter">
<f:selectItems value="#{invitationManagedBean.inviteAbleRoles}"
var="role"
itemValue="#{role}"
itemLabel="#{localized[role.concat('RoleName')]}">
</f:selectItems>
</p:selectOneMenu>
And you need to make sure that your Role implements equals and hashcode.

p:dataExporter does not recognize p:cellEditor

I have an editable <p:dataTable> with <p:cellEditor> and I want to export the content of that table into PDF format using <p:dataExporter>.
I have included the itext 2.1.7 jar. I got the output in PDF but it shows the Object#toString() values of all <p:cellEditor> components like so:
org.primefaces.component.celleditor.CellEditor#1bd59e1
How do I export the output values of the <p:cellEditor> instead?
The <p:cellEditor> is indeed not recognized by the PrimeFaces standard data exporters.
I've previously reported this to the PF guys as issue 4013 with an example which not only mentions CellEditor, but also HtmlGraphicImage (we are using images to show boolean states, whose alt we'd like to show in PDF/XML/XLS/CSV reports).
First, create a new class which extends the standard PDFExporter like follows:
public class ExtendedPDFExporter extends PDFExporter {
#Override
protected String exportValue(FacesContext context, UIComponent component) {
if (component instanceof CellEditor) {
return exportValue(context, ((CellEditor) component).getFacet("output"));
}
else if (component instanceof HtmlGraphicImage) {
return (String) component.getAttributes().get("alt");
}
else {
return super.exportValue(context, component);
}
}
}
Then, to use it, call it programmatically instead of via <p:dataExporter>.
<p:dataTable binding="#{table}" editable="true" ...>
<p:column><p:cellEditor>...</p:cellEditor></p:column>
<p:column><p:cellEditor>...</p:cellEditor></p:column>
<p:column><p:cellEditor>...</p:cellEditor></p:column>
<p:column exportable="false"><p:rowEditor /></p:column>
</p:dataTable>
<h:commandLink value="PDF" action="#{bean.exportPDF(table, 'filename')}" />
With
public void exportPDF(DataTable table, String filename) throws IOException {
FacesContext context = FacesContext.getCurrentInstance();
Exporter exporter = new ExtendedPDFExporter();
exporter.export(context, table, filename, false, false, "UTF-8", null, null);
context.responseComplete();
}
Feel free to find the data table by UIComponent#findComponent() instead and to set the filename in action method only. The above code is just exemplary.
I agree, I also find this approach to customize the Exporter behaviour the most flexible and least painful.
Anyone interested in using the preProcessor/postProcessor methods with this? Here's an example how to do that.
I dared to slightly modify the method from the answer above:
public void exportPDF(DataTable table, String filename,
String preProcessor, String postProcessor) throws IOException {
FacesContext context = FacesContext.getCurrentInstance();
ExpressionFactory factory = context.getApplication().getExpressionFactory();
MethodExpression preProcessorME = factory.createMethodExpression(
context.getELContext(), preProcessor, null, new Class[] {Object.class});
MethodExpression postProcessorME = factory.createMethodExpression(
context.getELContext(), postProcessor, null, new Class[] {Object.class});
Exporter exporter = new ExtendedPDFExporter();
exporter.export(context, table, filename, false, false, "UTF-8",
preProcessorMe, postProcessorME);
context.responseComplete();
}
And this is how you use it in your page (again, I just modified the above example):
<p:dataTable binding="#{table}" editable="true" ...>
<p:column><p:cellEditor>...</p:cellEditor></p:column>
<p:column><p:cellEditor>...</p:cellEditor></p:column>
<p:column><p:cellEditor>...</p:cellEditor></p:column>
<p:column exportable="false"><p:rowEditor /></p:column>
</p:dataTable>
<h:commandLink value="PDF" action="#{bean.exportPDF(table, 'filename',
'#{yourBean.preProcessPDF}', '#{yourBean.postProcessPDF}')}" />
Notice that there ARE NO NESTED EL STATEMENTS (that is not allowed anyway), the last two arguments are simple Strings containing EL expressions.

jsf datatable row selection problem

I have a problem with selecting rows in the Primefaces Datatable. I use dynamic columns, so the standard row selection mechanism is not usable here, I implement checkbox selection myself.
To help, here's s simplified version of what I have in my xhtml:
<h:form>
<p:dataTable id="table"
var="result"
value="#{tableBean.results}">
<p:columns value="#{tableBean.columnNames}" var="column" columnIndexVar="colIndex">
<f:facet name="header">
#{column}
</f:facet>
<h:panelGroup rendered="#{colIndex==0}">
<h:outputLabel>#{rowIndex}</h:outputLabel>
<h:selectBooleanCheckbox value="#{tableBean.selectedRows[result[0]]}"/>
</h:panelGroup>
</p:columns>
</p:dataTable>
<h:commandButton value="Submit"></h:commandButton>
</h:form>
And here's what I have in the managed bean to select the checkboxes:
package testpackage;
import java.util.*;
import javax.faces.bean.*;
#ManagedBean
#SessionScoped
public class TableBean
{
private Map<String, Boolean> selectedRows = new HashMap<String, Boolean>();
List<List<String>> results = new LinkedList<List<String>>();
public TableBean()
{
List<String> row1 = new LinkedList<String>();
List<String> row2 = new LinkedList<String>();
row1.add("row1.ref");
row1.add("row1.id");
row1.add("row1.status");
row2.add("row2.ref");
row2.add("row2.id");
row2.add("row2.status");
results.add(row1);
results.add(row2);
//selectedRows.put("row2.ref", true);
}
public Map<String, Boolean> getSelectedRows()
{
return selectedRows;
}
public String submit()
{
List<List<String>> selectedResults = new ArrayList<List<String>>();
for (List<String> result : results)
{
if (selectedRows.get(result.get(0)) != null)
{
selectedResults.add(result);
selectedRows.remove(result.get(0));
}
}
return null;
}
public List<List<String>> getResults()
{
return results;
}
public List<String> getColumnNames()
{
List<String> columnNames = new LinkedList<String>();
columnNames.add("");
columnNames.add("REF");
columnNames.add("ID");
columnNames.add("STATUS");
return columnNames;
}
}
The getSelectedRows method works great, but the problem is that the setSelectedRows method is never called, so I don't know which checkboxes the user has selected. Maybe I overlook something very trivial, but cannot find the solution.
Any ideas on this? I would be very glad if you helped, or give any other row selection solution for the dynamic columns.
Thx in advance,
Levi
To me it looks you are rendering the wrong field in selectBooleanCheckBox.
You should be using variable or field from the result variable.
My solution:
In your situation you are rendering an object from List as a form of table row so if you want to make some changes and retrieve the status of that row then you should be using the variable from that object only.
I understand you are submitting the whole form and want to pickup all updated rows, in that case you will have to loop through the whole List and find all the rows which have been updated by checking the status in Request Handler(Action) bean.
Hope that helps.
The setter is never called for nested objects. You're the one who's responsible for creating them, not JSF. JSF just gets the nested object and then calls the setter on it (which is the put() method in case of a Map). You just need to determine the selected rows in the action method. Add an action method to the commandbutton:
<h:commandButton value="Submit" action="#{bean.submit}"></h:commandButton>
which is definied like follows (guessing/assuming that var="result" is in essence an Object[]):
public String submit() {
List<Object[]> selectedResults = new ArrayList<Object[]>();
for (Object[] result : results) {
if (selectedRows.get((String) result[0])) {
selectedResults.add(result);
selectedRows.remove(result[0]); // Reset.
}
}
// Now selectedResults contains all selected results.
}

Resources