Can JavaFX TableView be partly editable? - javafx-2

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

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.

How to use Primefaces Dialog with #ViewScoped setting Parameters?

I'm facing again problem with Dialog.
What I intend to do is a common dialog that will be used for the whole appication, It has its own managed bean that is inherited by other MBs that need to use it, then some parameters are set by example super.setParam(...) to set some data to be displayed.The problem is that when the dialog is being loaded a getter method is called to retrieve the parameters set and it is no longer there, its just null.
I believe that due to the MB being #ViewScoped the container is creating a new instance when the dialog is beying loaded, but the setter method was called before it, so the new instance return to the default values. Using a #SessionScoped would solve the problem but it is not a good choice.
As a work around I tried to set the parameter in the request and then read it in the getter method but it is no longer there either.
Is there some way to get it working?
//here I put the parameter
public void setParam(MyObject myObject) {
FacesContext.getCurrentInstance().getExternalContext().getRequestMap().put("params", myObject);
public MyObject getMyObject() {
// now the parameter is no longer there...
Object objet = FacesContext.getCurrentInstance().getExternalContext().getRequestMap().get("params");
.... after doing some things
return anotherObject;
}
}
EDIT<<<<
All starts with this button on a "client.xhtml"
<p:commandButton value="Call Dialog" ajax="true"
actionListener="#{subDialogMB.startProcess}"
>
</p:commandButton>
#javax.faces.bean.ManagedBean
#javax.faces.bean.ViewScoped
public class SubDialogMB extends SuperDialogMBean() {
public void starProcess() {
try {
MyObject myObject = service.CreateMyObject....
super.setMyObject();
super.shpwDialog();
}
}
#javax.faces.bean.ManagedBean
public class SuperDialogMBean() {
public void setMyObject(MyObject myObject) {
FacesContext.getCurrentInstance().getExternalContext().getRequestMap().put("params", myObject);
}
public MyObject getMyObject(){
public MyObject getMyObject() {
// now the parameter is no longer there...
Object objet = FacesContext.getCurrentInstance().getExternalContext().getRequestMap().get("params");
.... after doing some things
return anotherObject;
}
}
public void showDialog() {
Map<String,Object> options = new HashMap<String, Object>();
options.put("modal", true);
options.put("draggable", false);
options.put("resizable", false);
options.put("contentHeight", 900);
options.put("contentWidth", 1100);
RequestContext.getCurrentInstance().openDialog("myDialog", options, null);
}
}
and finally in the Dialog xhtml
<p:outputText value="#{superDialogMBean.someValue}" />

actionListener commandLink not working on lazy dataScroller

In a project i need to lazy load object from database, and for each element i will put a link to redirect to a specific page.
The lazy loading is working. and when a click on the link for the first element it's ok, the problem is after scrolling, the next element don't call the listner listOi.editer().
<p:dataScroller value="#{listOi.lazyOi}" var="oi" id="OisChoser" widgetVar="scroller"
chunkSize="5" mode="inline" scrollHeight="531" lazy="true" style="width: 597px;" rows="5" >
<h:panelGroup id="info_OI" class="info_OI" align="center" >
...
<h:commandLink actionListener="#{listOi.editer()}" immediate="true" >
<f:param name="selectedoiId" value="#{oi.id}" />
<span class="crayon" style='cursor: pointer;'></span>
</h:commandLink>
...
</h:panelGroup
</p:dataScroller>
The problem is that PrimeFaces' LazyDataModel does not keep a complete model of the data displayed in the view. It only keeps track of the most recently loaded items and discards older items. This makes that these items are no longer accessible from JSF.
But since you are subclassing that class anyway (it's abstract), it's pretty easy to alter that behavior. Basically what you want to do is keep track of all the data you loaded so far and return that data as asked for. At the minimum, you need to override setRowIndex(), getRowData(), isRowAvailable() and load(). Below is an example that works for me.
public class MyLazyModel extends LazyDataModel<SomeType> implements Serializable {
private final List<SomeType> data;
private int rowIndex;
public MyLazyModel() {
super();
data = new ArrayList<>();
}
#Override
public List<SomeType> load(int first, int pageSize, String sortField, SortOrder sortOrder, Map<String, Object> filters) {
List<SomeType> retData;
if (first >= data.size()) {
retData = ... //Get the data from datasource
data.addAll(retData);
return retData;
} else {
return data.subList(first, Math.min(first + pageSize, data.size()));
}
}
#Override
public void setRowIndex(int index) {
if (index >= data.size()) {
index = -1;
}
this.rowIndex = index;
}
#Override
public SomeType getRowData() {
return data.get(rowIndex);
}
#Override
public boolean isRowAvailable() {
if (data == null) {
return false;
}
return rowIndex >= 0 && rowIndex < data.size();
}
}
Because you can never be sure that setWrappedData() (which is called after load() with the return data from that method) is called only once, you need to guard against adding doubles to your data model. Here I do this by only loading data that has never been loaded before and storing that data in the load() method, ignoring setWrappedData() completely. It's quite ugly and will lead to synchronization problems if your model is never invalidated, but it works. Anyway, you could circumvent this by always loading data and replacing old content with new, but that's not the core of the issue.
Also, because you now keep track of the data in the method itself, you need to override all methods in LazyDataModel that rely on LazyDataModel.data being correct (or at least that subset of them that you are using).
Final note: you of course have to make sure that your model returned to the JSF page is always the same, as discussed here.

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

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>

Convert h:selectBooleanCheckbox value between boolean and String

I have a backing bean containing a field creditCard which can have two string values y or n populated from the DB. I would like to display this in checkbox so that y and n gets converted to boolean.
How can I implement it? I can't use a custom converter as getAsString() returns String while rendering the response whereas I need a boolean.
The <h:selectBooleanCheckbox> component does not support a custom converter. The property has to be a boolean. Period.
Best what you can do is to do the conversion in the persistence layer or to add extra boolean getter/setter which decorates the original y/n getter/setter or to just replace the old getter/setter altogether. E.g.
private String useCreditcard; // I'd rather use a char, but ala.
public boolean isUseCreditcard() {
return "y".equals(useCreditcard);
}
public void setUseCreditcard(boolean useCreditcard) {
this.useCreditcard = useCreditcard ? "y" : "n";
}
and then use it in the <h:selectBooleanCheckbox> instead.
<h:selectBooleanCheckbox value="#{bean.useCreditcard}" />
You can use the BooleanConverter for java primitives, this parses the text to boolean in your managedbean, at here just put in your code like this in you .xhtml file
<p:selectOneMenu id="id"
value="#{yourMB.booleanproperty}"
style="width:60px" converter="javax.faces.Boolean">
<p:ajax listener="#{yourMB.anylistener}"
update="anyIDcontrol" />
<f:selectItem itemLabel="------" itemValue="#{null}"
noSelectionOption="true" />
<f:selectItem itemLabel="y" itemValue="true" />
<f:selectItem itemLabel="n" itemValue="false" />
</p:selectOneMenu>
ManagedBean:
#ManagedBean(name="yourMB")
#ViewScoped
public class YourMB implements Serializable {
private boolean booleanproperty;
public boolean isBooleanproperty() {
return booleanproperty;
}
public void setBooleanproperty(boolean booleanproperty) {
this.booleanproperty = booleanproperty;
}
}
I had the similar problem, and I agree with previous post, you should handle this issues in persistence layer.
However, there are other solutions. My problem was next: I have TINYINT column in database which represented boolean true or false (0=false, 1=true). So, I wanted to display them and handle as a boolean in my JSF application. Unfortunately, that was not quite possible or just I didn't find a proper way. But instead using checkbox, my solution was to use selectOneMeny and to convert those values to "Yes" or "No". Here is the code, so someone with similar problem could use it.
Converter:
#FacesConverter("booleanConverter")
public class BooleanConverter implements Converter{
#Override
public Object getAsObject(FacesContext context, UIComponent component, String value) {
short number= 0;
try {
if (value.equals("Yes")) {
number= 1;
}
} catch (Exception ex) {
FacesMessage message = new FacesMessage();
message.setSeverity(FacesMessage.SEVERITY_FATAL);
message.setSummary(MessageSelector.getMessage("error"));
message.setDetail(MessageSelector.getMessage("conversion_failed") + ex.getMessage());
throw new ConverterException(message);
}
return number;
//throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
}
#Override
public String getAsString(FacesContext context, UIComponent component, Object value) {
return value.toString();
//throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
}
JSF Page:
<h:selectOneMenu id="selectOnePlayerSucc" value="#{vezbaTrening.izvedenaUspesno}" converter="booleanConverter">
<f:selectItems id="itemsPlayerSucc" value="#{trainingOverview.bool}" var="opt" itemValue="#{opt}" itemLabel="#{opt}"></f:selectItems>
And in my ManagedBean I created a list with possible values ("Yes" and "No")
private List<String> bool;
public List<String> getBool() {
return bool;
}
public void setBool(List<String> bool) {
this.bool = bool;
#PostConstruct
public void init () {
...
bool = new LinkedList<>();
bool.add("Yes");
bool.add("No");
}

Resources