How to get column ID via #{component.id} in EL? - jsf

I just want to send the Column ID as a parameter (and I don't want to write it twice, of course).
I used to make use of component.id but it returns DataTable ID instead of Column ID:
<p:dataTable id="table_id" ... >
<p:column id="column_id" attr="#{bean.method(component.id)}"
</p:dataTable>

The DataTableRenderer of PrimeFaces indeed never pushes the UIColumn component into EL as #{component} when it's about to render the header, cell and footer of a column. It's fortunately relatively easy to override it to do so.
Create a class which extends it and then override the encodeColumnHeader(), encodeCell() and encodeColumnFooter() to first push the given Column component (you need to downcast the UIColumn argument first; you may want to perform an instanceof check if you're also using the "dynamic columns" feature) before delegating to super. Don't forget to pop in finally to ensure that EL don't get polluted with wrong #{component} state in case of an exception.
package com.stackoverflow.q25464066;
import java.io.IOException;
import javax.faces.context.FacesContext;
import org.primefaces.component.api.UIColumn;
import org.primefaces.component.column.Column;
import org.primefaces.component.datatable.DataTable;
import org.primefaces.component.datatable.DataTableRenderer;
public class ExtendedDataTableRenderer extends DataTableRenderer {
#Override
protected void encodeColumnHeader(FacesContext context, DataTable table, UIColumn column) throws IOException {
table.pushComponentToEL(context, (Column) column);
try {
super.encodeColumnHeader(context, table, column);
}
finally {
table.popComponentFromEL(context);
}
}
#Override
protected void encodeCell(FacesContext context, DataTable table, UIColumn column, String clientId, boolean selected) throws IOException {
table.pushComponentToEL(context, (Column) column);
try {
super.encodeCell(context, table, column, clientId, selected);
}
finally {
table.popComponentFromEL(context);
}
}
#Override
protected void encodeColumnFooter(FacesContext context, DataTable table, UIColumn column) throws IOException {
table.pushComponentToEL(context, (Column) column);
try {
super.encodeColumnFooter(context, table, column);
}
finally {
table.popComponentFromEL(context);
}
}
}
To get it to run, register it as follows in faces-config.xml:
<render-kit>
<renderer>
<description>Overrides the PrimeFaces table renderer with improved #{component} support.</description>
<component-family>org.primefaces.component</component-family>
<renderer-type>org.primefaces.component.DataTableRenderer</renderer-type>
<renderer-class>com.stackoverflow.q25464066.ExtendedDataTableRenderer</renderer-class>
</renderer>
</render-kit>

Related

Disable one checkbox out of many in <h:selectManyCheckbox where checkboxes come from a LinkedHashMap [duplicate]

I need your help in disabling and enabling an item from the selectManyCheckbox component in a jsf page. First of all, the selectManyCheckbox component is showing three chechboxes which are (Loan - Health - Transfer). The list will be populated from a bean which it has the code:
private List<hrCertificate> hrCertificatesList = new ArrayList<hrCertificate>();
//Getter and Setter
Private String loanFlag="";
#PostConstruct
public void init() {
this.hrCertificatesList.add(new hrCertificate(("Loan"), "LC"));
this.hrCertificatesList.add(new hrCertificate(("Health"), "HI"));
this.hrCertificatesList.add(new hrCertificate(("Trasnfer"), "TE"));
}
In the same bean, I will be running a SQL statement that will return either Yes or No and that value I am adding it to the loanFlag variable.So if the flag="Y", I need to enable the loan checkbox so the user can select it else I need to disable it from the selectManyCheckbox. The issue is that I am facing difficulties in applying the logic to disable and to enable the item selectManyCheckboxwhere in the above code I am listing and enabling them all the time.
The code for the selectManyChexkbox:
<p:selectManyCheckbox id="hrCertificates" value="#{user.selectedHRCertificates}" layout="pageDirectio>
<f:selectItems value="#{user.hrCertificatesList}"
var="hrCertificate" itemLabel="#{hrCertificate.hrCertificateName}"
itemValue="#{hrCertificate.hrCertificateCode}"/>
</p:selectManyCheckbox>
So how to apply the logic
Could you edit your hrCertificate class to add a disabled boolean field? If yes, then you can add itemDisabled="#{hrCerticate.disabled}" to your f:selectItems which should be the easiest solution.
Another option would be to use a Map<hrCertificate, Boolean> instead of a List<hrCertificate>.
private Map<hrCertificate, Boolean> hrCertificatesMap = new HashMap<hrCertificate, Boolean>();
#PostConstruct
public void init() {
hrCertificatesMap.put(new hrCertificate(("Loan"), "LC"), null);
hrCertificatesMap.put(new hrCertificate(("Health"), "HI"), null);
hrCertificatesMap.put(new hrCertificate(("Trasnfer"), "TE"), null);
}
// Then when you're done with your SQL query, update your Map to add the corresponding boolean values...
.xhtml
<p:selectManyCheckbox id="hrCertificates" value="#{user.selectedHRCertificates}" layout="pageDirectio>
<f:selectItems value="#{user.hrCertificatesMap.keySet().toArray()}" var="hrCertificate" itemLabel="#{hrCertificate.hrCertificateName}" itemValue="#{hrCertificate.hrCertificateCode}" itemDisabled="#{user.hrCertificatesMap.get(hrCertificate)}" />
</p:selectManyCheckbox>
First, note that a property does not retire an actual attribute backing it, you only need a getter. So you can have:
public class MyBean implements Serializable {
private FilterEnum certFilter = FilterEnum.NO_FILTER;
private List<Certificate> certificates;
... // including certificates initialization.
public FilterEnum getCertFilter() {
return this.certFilter;
}
public void setCertFilter(FilterEnum certFilter) {
this.certFilter = certFilter;
}
public List<Certificate> getCertificates() {
// I am sure there is a cooler way to do the same with streams in Java 8
ArrayList<Certificate> returnValue = new ArrayList<>();
for (Certificate certificate : this.certificates) {
switch (this.certFilter) {
case FilterEnum.NO_FILTER:
returnValue.add(certificate);
break;
case FilterEnum.ONLY_YES:
if (certificate.isLoan) {
returnValue.add(certificate);
}
break;
case FilterEnum.ONLY_NO:
if (!certificate.isLoan) {
returnValue.add(certificate);
}
break;
}
}
return returnValue;
}
}
If you insist that you want to do the filter "in the .xhtml", you can combine c:forEach from JSTL with <f:selectItem> (note item, not items), but it will make your xhtml more complicated and may cause issues if you want to use Ajax with it.

Can I have a panelGroup linked to a listener without f:ajax listener?

I've replaced the f:ajax tag with an homemade solution that doesn't put inline script. It works wonder for actionButton. However I cannot make it work for a listener on a panelGroup. The reason is that it is specified nowhere what the bean target method resulting from the ajax request should be. In other words with a commandButton I can specify the target bean method in action, but there is no such attribute for panelGroup; as I don't want to use f:ajax listener, I want to replace it.
<h:commandButton data-widget="jsfajax" value="ajax" action="#{someAction}"/>
$(document).ready(function(){
(function(widgets){
document.body.addEventListener("click", function(e) {
var w = e.target.getAttribute("data-widget");
if(w){
e.preventDefault();
widgets[w](e.target);
}
});
})(new Widgets);
});
function Widgets(){
this.jsfajax = function jsfajax(elem){
if(elem.id == ""){
elem.id = elem.name;
}
mojarra.ab(elem,"click",'action','#form',0);
}
}
This works.
But this obviously doesn't (it does but it doesn't invoke anything) :
<h:panelGroup>
<f:passThroughAttribute name="data-widget" value="jsfajax"/>
Click here
</h:panelGroup>
But this does :
<h:panelGroup>
<f:ajax event="click" listener="#{someAction}"/>
Click here
</h:panelGroup>
Both those panelGroup result in the same HTML output, so I assume it's the jsf container which "remembers" the click on that panelGroup is linked to #{someAction}.
What I'd like to do is recreate that link without using f:ajax listener. At the moment I've to use an hidden commandButton which is less elegant.
So maybe a composite component panelGroup which would save the "action link", I've no idea.
What you want to achieve is only possible on UICommand components, not on ClientBehaviorHolder components. One solution would be to create a custom component extending HtmlCommandLink which renders a <div> instead of <a> and use it like so <your:div action="#{bean.action}">.
The most ideal solution would be to replace the standard renderers. E.g. for <h:panelGorup>:
<render-kit>
<renderer>
<component-family>javax.faces.Panel</component-family>
<renderer-type>javax.faces.Group</renderer-type>
<renderer-class>com.example.YourPanelGroupRenderer</renderer-class>
</renderer>
</render-kit>
Basically, those renderers should skip rendering <f:ajax>-related on* attributes and instead render your data-widget attribute (and preferably also other attributes representing existing <f:ajax> attributes such as execute, render, delay, etc). You should also program against the standard API, not the Mojarra-specific API. I.e. use jsf.ajax.request() directly instead of mojarra.ab() shortcut.
This way you can keep your view identical conform the JSF standards. You and future developers would this way not even need to learn/think about a "proprietary" API while writing JSF code. You just continue using <h:panelGroup><f:ajax>. You simply plug in the custom renders and script via a JAR in webapp and you're done. That JAR would even be reusable on all other existing JSF applications. It could even become popular, because inline scripts are indeed considered poor practice.
It's only quite some code and not necessarily trivial for a starter.
A different approach is to replace the standard response writer with a custom one wherein you override writeAttribute() and check if the attribute name starts with on and then handle them accordingly the way you had in mind. E.g. parsing it and writing a different attribute. Here's a kickoff example which also recognizes <h:panelGroup><f:ajax>.
public class NoInlineScriptRenderKitFactory extends RenderKitFactory {
private RenderKitFactory wrapped;
public NoInlineScriptRenderKitFactory(RenderKitFactory wrapped) {
this.wrapped = wrapped;
}
#Override
public void addRenderKit(String renderKitId, RenderKit renderKit) {
wrapped.addRenderKit(renderKitId, renderKit);
}
#Override
public RenderKit getRenderKit(FacesContext context, String renderKitId) {
RenderKit renderKit = wrapped.getRenderKit(context, renderKitId);
return (HTML_BASIC_RENDER_KIT.equals(renderKitId)) ? new NoInlineScriptRenderKit(renderKit) : renderKit;
}
#Override
public Iterator<String> getRenderKitIds() {
return wrapped.getRenderKitIds();
}
}
public class NoInlineScriptRenderKit extends RenderKitWrapper {
private RenderKit wrapped;
public NoInlineScriptRenderKit(RenderKit wrapped) {
this.wrapped = wrapped;
}
#Override
public ResponseWriter createResponseWriter(Writer writer, String contentTypeList, String characterEncoding) {
return new NoInlineScriptResponseWriter(super.createResponseWriter(writer, contentTypeList, characterEncoding));
}
#Override
public RenderKit getWrapped() {
return wrapped;
}
}
public class NoInlineScriptResponseWriter extends ResponseWriterWrapper {
private ResponseWriter wrapped;
public NoInlineScriptResponseWriter(ResponseWriter wrapped) {
this.wrapped = wrapped;
}
#Override
public ResponseWriter cloneWithWriter(Writer writer) {
return new NoInlineScriptResponseWriter(super.cloneWithWriter(writer));
}
#Override
public void writeAttribute(String name, Object value, String property) throws IOException {
if (name.startsWith("on")) {
if (value != null && value.toString().startsWith("mojarra.ab(")) {
super.writeAttribute("data-widget", "jsfajax", property);
}
}
else {
super.writeAttribute(name, value, property);
}
}
#Override
public ResponseWriter getWrapped() {
return wrapped;
}
}
The most important part where you have your freedom is the writeAttribute() method in the last snippet. The above kickoff example just blindly checks if the on* attribute value starts with Mojarra-specific "mojarra.ab(" and then instead writes your data-widget="jsfajax". In other words, every single (naturally used!) <f:ajax> will be rewritten this way. You can continue using <h:commandLink><f:ajax> and <h:panelGroup><f:ajax> the natural way. Don't forget to deal with other <f:ajax> attributes while you're at it.
In order to get it to run, register as below in faces-config.xml:
<factory>
<render-kit-factory>com.example.NoInlineScriptRenderKitFactory</render-kit-factory>
</factory>
You only still need to take into account existing implementation-specific details (fortunately there are only two: Mojarra and MyFaces).
See also:
How do I determine the renderer of a built-in component

Can JavaFX TableView be partly editable?

I've got a JavaFX TableView which possesses two columns. This tables uses a ObservableList gasRatioMeasures as its model.
public class GasRatioMeasureBean {
private String number;
private String measure;
public String getNumber() {
return number;
}
public void setNumber(int number) {
this.number = "Measure" + (number + 1) + "(%)";
}
public String getMeasure() {
return measure;
}
public void setMeasure(String measure) {
this.measure = measure;
}
}
I'd like to set one of them editable and the other non-editable.
Firstly I tried the FXML way:
<TableView layoutX="24.0" layoutY="122.0" prefHeight="200.0" prefWidth="215.0" fx:id="measureTableView">
<columns>
<TableColumn editable="false" prefWidth="100.0" sortable="false" text="No" fx:id="measureNumbersColumn" />
<TableColumn editable="true" prefWidth="110.0" sortable="false" text="Measures" fx:id="measuresColumn" />
</columns>
</TableView>
But this didn't work. The table is always not editable.
I also tried out the java way:
private void initMeasuresTableView() {
measureNumbersColumn.setCellValueFactory(new PropertyValueFactory<GasRatioMeasureBean, String>("number"));
measureNumbersColumn.setEditable(false);
measuresColumn.setCellValueFactory(new PropertyValueFactory<GasRatioMeasureBean, String>("measure"));
measuresColumn.setOnEditCommit(
new EventHandler<CellEditEvent<GasRatioMeasureBean, String>>() {
public void handle(CellEditEvent<GasRatioMeasureBean, String> measure) {
((GasRatioMeasureBean) measure.getTableView().getItems().get(
measure.getTablePosition().getRow())
).setMeasure(measure.getNewValue());
}
}
);
measureTableView.setItems(gasRatioMeasures);
measureTableView.setEditable(true);
}
But this didn't work either. All the table kept uneditable.
If TableColumn has an editable property, then it should be able to be set separately.
Please help me if you know where I'm doing wrong. Thanks!
PS: the column measureNumbersColumn can already been displayed normally (when I haven't initialized measuresColumn).
PPS: I also tried instead of setOnEditCommit setOnEditStart . setOnEditCommit is not called, but setOnEditStart can be called just in editable column.
You did not use a cell factory that supports editing. You need to do:
measureNumbersColumn.setCellFactory(TextFieldTableCell.forTableColumn())
This will make your column use a TextFieldTableCell instead of the default TableCell.
TextFieldTableCell supports editing (look at the source code and look for startEdit method).
Alternatively if you are not happy with the behavior of the TextFieldTableCell, you can write your own implementation of TableCell, and override the startEdit, cancelEdit, and updateItem methods.
measureNumbersColumn.setCellFactory(new Callback<TableColumn<GasRatioMeasureBean,String>, TableCell<GasRatioMeasureBean,String>>() {
#Override
public TableCell<GasRatioMeasureBean, String> call(
TableColumn<GasRatioMeasureBean, String> arg0) {
return MyOwnTableCell();
}
});
This is the way by which you can make editable columns in TableView.
tblViewPerson.setEditable(true);
// Making editable columns
// For ID
tblColID.setCellFactory(TextFieldTableCell.forTableColumn());
tblColID.setOnEditCommit(
new EventHandler<CellEditEvent<Person, String>>() {
public void handle(CellEditEvent<Person, String> t) {
((Person) t.getTableView().getItems().get(
t.getTablePosition().getRow())
).setId(t.getNewValue());
}
}
);

Validate rich:dataTable value size's on form submit

I have a "new item" form that requires a list of dates, with the following components:
A <rich:calendar> input;
A <a4j:commandButton> that adds the chosen date to a List<Date> chosenDates in the backing bean;
A <rich:dataTable> with it's value set to the List<Date> chosenDates attribute;
A <a4j:commandButton> per dataTable row that removes it's date from theList<Date> chosenDates;
How to validate (JSF's validation phase) the size of the chosenDates list on form submit (creation process)?
RichFaces 4, JSF 2.1 (Mojarra).
I'd advise a cleaner approach with a JSF PhaseListener. The JSF processing will stop skip ahead the other phases if validation fails. Create a PhaseListener that will inspect the size of your list during the validations phase as against during the model update/invoke action phase. Try something like this
Create a phase listener for the validations phase
public class TestPhaseListener implements PhaseListener {
#Override
public void afterPhase(PhaseEvent event) {
throw new UnsupportedOperationException("Not supported yet.");
}
#Override
public void beforePhase(PhaseEvent event) {
if(event.getPhaseId().equals(PhaseId.PROCESS_VALIDATIONS)){
FacesContext ctx = event.getFacesContext();
YourBeanClass theBeanClass = ctx.getApplication().evaluateExpressionGet(ctx, "#{someBean}", YourNeanClass.class); //obtain a reference to the backing bean containing the list
/*
inspect the size of the list here and based on that throw the exception below
*/
throw new ValidatorException(new FacesMessage("Too many dates","Too Many Dates"));
}
}
#Override
public PhaseId getPhaseId() {
throw new UnsupportedOperationException("Not supported yet.");
}
}
Register your new listener in the faces_config.xml file
<lifecycle>
<phase-listener>your.package.structure.TestPhaseListener</phase-listener>
</lifecycle>
EDIT: Based on your comment, as an alternative, you can hook into the component's lifecycle using the <f:event/> tag and the preValidate or postValidate events (depending on your preference)
A listener tag to your component
<rich:dataTable>
<f:event type="preValidate" listener="#{yourBean.listener}"/>
</rich:dataTable>
Define a listener method in your backing bean to run per your defined event. The method signature must take an argument of type ComponentSystemEvent
public void preCheck(ComponentSystemEvent evt){
//You're in your backing bean so you can do pretty much whatever you want. I'd advise you mark the request as validation failed and queue FacesMessages. Obtain a reference to FacesContext and:
facesContext.validationFailed();
}
Do something like:
#{yourBean.chosenDates.size()}
I suppose you have a getter called getChosenDates which returns the chosenDates list.
Regarding your "validation concerns":
You can create a Validate method in your bean and return list of ValidationMessages. A sample is below, one that i used in my code.
public List<ValidationMessage> validate() {
List<ValidationMessage> validations = new ArrayList<ValidationMessage>();
int curSampleSize = sampleTable.getDataModel().getRowCount();
if(getNumberOfSamples() != null) {
size += getNumberOfSamples();
} else {
validations.add(new ValidationMessage("Please enter the no of samples to continue."));
return validations;
}
return validations;
}
Then, on submit you can check if you have any ValidationMessages as follows:
List<ValidationMessage> errs = validate();
if(errs.size()>0) {
FacesValidationUtil.addFacesMessages(errs);
return null;
}
Hope this helps!

p:dataTable multiple selection not working

I don't seem to get multiple selection in PrimeFaces dataTables working.
I'm trying to implement a list of clients (dataList) and show their respective bookings in nested dataTables with a possibility to select multiple bookings for billing:
<p:dataList value="#{clientController.allClients}" var="client">
<p:column>
<p:dataTable value='#{client.bookingsDataModel}' var='item' selection="#{client.bookingsToBill}">
<p:column selectionMode="multiple" />
</p:dataTable>
</p:column>
</p:dataList>
My controller and backing bean classes:
public class ClientController {
public List<Client> getAllClients() {
return clients;
}
}
public class Client {
private List<Booking> bookings;
private Booking[] bookingsToBill;
public LeistungDataModel getBookingsDataModel() {
return new BookingsDataModel(bookings);
}
public Booking[] getBookingsToBill() {
return bookingsToBill;
}
public void setBookingsToBill(Booking[] bookingsToBill) {
this.bookingsToBill = bookingsToBill;
}
}
The data model class:
public class BookingsDataModel extends ListDataModel<Booking> implements SelectableDataModel<Booking> {
public BookingsDataModel(List<Booking> data) {
super(data);
}
#Override
public Booking getRowData(String rowKey) {
List<Booking> bookings = (List<Booking>) getWrappedData();
for(Booking booking : bookings) {
if(("booking_"+booking.getId().toString()).equals(rowKey)) {
return booking;
}
}
return null;
}
#Override
public Object getRowKey(Booking booking) {
return "booking_"+booking.getId().toString();
}
}
The browser posts the following data to the server, when I submit the form with my selections:
j_idt9%3Aj_idt13%3A0%3Aj_idt15_selection:booking_300,booking_301,booking_302
j_idt9%3Aj_idt13%3A1%3Aj_idt15_selection:booking_566,booking_567
j_idt9%3Aj_idt13%3A2%3Aj_idt15_selection:
Also, I found during debugging that the getRowData method of the BookingsDataModel returns the correct Booking objects (the selected ones).
However, always empty arrays are passed to the setBookingsToBill of my Client objects. What could be going wrong here?
Update:
An empty array is only passed the first Client objects - it doesn't matter if a booking has been selected or not. All other Client objects' setBookingsToBill methods are called with a parameter value of null.
Not really, if you want multiple selection with check box you have to do as jfs did:
In the showcase there is one example showing just that. It will create a column containing the boxes for the user to select. You can also do as you've said, using an attribute of p:dataTable, however this will not create the boxes and the user will have to control+click to do multiple select.
The selectionMode should be a part of the <p:dataTable> tag.
Here is a link to the showcase which has an example.
http://www.primefaces.org/showcase/ui/datatableRowSelectionMultiple.jsf

Resources