I have filtered a JTable with following RowFilter and it works fine:
RowFilter<AbstractTableModel, Object> rowFilter = new RowFilter<AbstractTableModel, Object>(){
public boolean include(RowFilter.Entry<? extends AbstractTableModel, ? extends Object> entry){
String cellValue = entry.getValue(0).toString();
boolean isRowIncluded = cellValue.startsWith(filterText) ? true : false;
return isRowIncluded;
}
};
But I am facing an issue. I have to apply the same filter logic to TreeTable. I have a custom table and table model extended from AbstractTableModel. I need to filter the rows and show its parents rows (parent rows may not satisfy isRowIncluded).
How can I achieve the functionality with my existing row filter?
Related
I have vaadin grid, and it's great that it has lazy data loading from the box. But for some reasons I have custom filters, which I use via
CallbackDataProvider<> dataProvider.fetch(Query query)
Query object has parameters for loading by portions (offset and limit), so I need to set it dynamically (?) and somehow listen grid scrolling event to load next part of data when user scrolls down (?)
Grid.dataComunicator has field Range pushRows but there no public methods to get it. And all i have is grid with lazy loading without filtered data or grid with eager loading with filtered data.
So, is there any way to implement filtering data with lazy loading in vaadin grid element?
ok, problem solved by using ConfigurableFilterDataProvider<> as wrapper over CallbackDataProvider<>.
so, when i filter table, this wrapper adds filtering conditions to all queries, and data loads lazy as usual.
I arrived here using vaadin 22. The answer probably isn't in the same context as the question but given I arrived here I suspect others will.
To create a grid that uses lazy loading and is able to inject a filter into the query use:
class SearchableGrid<E> {
Grid<E> entityGrid = new Grid<>();
private SearchableGrid(DaoDataProvider daoProvider)
{
var view = entityGrid.setItems(query ->
{
// add the filter to the query
var q = new Query<E, String>(query.getOffset(), query.getLimit(), query.getSortOrders(), null,
getSearchField().getValue());
return daoProvider.fetchFromBackEnd(q);
});
view.setItemCountCallback(query ->
{
// add the filter to the query
var q = new Query<E, String>(query.getOffset(), query.getLimit(), query.getSortOrders(), null,
getSearchField().getValue());
return daoProvider.sizeInBackEnd(q);
});
}
I've packaged the methods into a BackEndDataProvider as the same class
can be used to as a provider for comboboxes.
public class DaoDataProvider<E extends CrudEntity>
extends AbstractBackEndDataProvider<E, String>
{
JpaBaseDao<E> dao;
GetFilterBuilder<E> getFilterBuilder;
public DaoDataProvider(JpaBaseDao<E> daoProvider, GetFilterBuilder<E> getFilterBuilder)
{
this.dao = daoProvider;
this.getFilterBuilder = getFilterBuilder;
}
#Override
public int sizeInBackEnd(Query<E, String> query)
{
var q = getFilterBuilder.builderFilter(query);
return (int) q.count().intValue();
}
#Override
public Stream<E> fetchFromBackEnd(Query<E, String> query)
{
var q = getFilterBuilder.builderFilter(query);
q.startPosition(query.getOffset()).limit(query.getLimit());
return q.getResultList().stream();
}
}
The filterBuilder is where you construct your query for your back end data provider.
I am trying to extends GWT's DataGrid capabilities for my own project and would like to add the ability to filer columns. I have successfully rendered a filter box in the Header, but it is not responding to events.
Following is the relvant part of my code, which has been adapted from the code given here: CellTable with custom Header containing SearchBox and Focus Problem
The question above does not quite fit my needs, as it does not work if the columns are sortable.
Instead, I have developed a header consisted of 2 table rows (TR's), the top row containing filter boxes, the 2nd row containing column titles and responding to Sort events. The Sort events work OK, but the filter boxes to not respond to any events. Here's the code:
class HeaderBuilder extends AbstractHeaderOrFooterBuilder<Record> {
//HTML to render an Input Box
private InputBoxHTML inputBox = GWT.create(InputBoxHTML.class);
//List of columns in the table
private List<ListGridColumn<?>> columns = new ArrayList<ListGridColumn<?>>();
//Constructor. ListGrid is the outer class extending DataGrid
private HeaderBuilder() {
super(ListGrid.this, false);
}
#Override
protected boolean buildHeaderOrFooterImpl() {
TableRowBuilder tr = startRow();
tr.startTH().endTH(); //extra column
//Create top row of column headers - filter boxes for filterable columns, empty cells for non-filerable
for (ListGridColumn<?> column : this.columns) {
TableCellBuilder th = tr.startTH();
Header<String> header;
//If this column is filterable...
if (column.filter) {
//Create a new Cell containing an Input Box
AbstractCell<String> cell = new AbstractCell<String>("click","keydown","keyup") {
public void render(Context context, String value, SafeHtmlBuilder sb) {
sb.append(inputBox.input(""));
}
public void onBrowserEvent(Context context, Element parent, String value, NativeEvent event, ValueUpdater<String> valueUpdater) {
//These events never fire!
Window.alert("event");
}
};
header = new Header<String>(cell) {
public String getValue() {
return "value";
}
};
} else {
//Empty cell for non-filterable columns
header = new TextHeader("");
}
Context context = new Context(0, 0, header.getKey());
renderHeader(th, context, header);
th.endTH();
}
tr.endTR();
//Bottom row : header captions & sorting. This all works OK
tr = startRow();
tr.startTH().endTH(); //extra column
for (ListGridColumn<?> column : this.columns) {
TableCellBuilder th = tr.startTH();
enableColumnHandlers(th, column);
Header<String> header = new TextHeader(column.headerStr);
Context context = new Context(0, 0, header.getKey());
if (column.sortKey!=null) {
this.renderSortableHeader(th, context, header, true, true);
} else {
this.renderHeader(th, context, header);
}
th.endTH();
}
tr.endTR();
return true;
}
}
If you looks in the source code of insertColumn(int beforeIndex, Column col, Header header, Header footer) method in AbstractCellTable class (which is extended by DataGrid and CellTable), when a column is inserted (or added) all the events for the header (cell or footer) are sinked in order to propagate it from the table to the corresponding cell:
if (header != null) {
Set<String> headerEvents = header.getCell().getConsumedEvents();
if (headerEvents != null) {
consumedEvents.addAll(headerEvents);
}
}
...
CellBasedWidgetImpl.get().sinkEvents(this, consumedEvents);
You're declaring the Headers in the builder but not "registering" it, therefore events are not propagated to the header cell. You should find a way to register it. I don't see any clean solution because DataGrid can not be easily extended.
I can purpose you two dirty ones:
Create your version of DataGrid (you need to copy and paste the code and declared it in the same package of DataGrid) and modify it in order to register two header for column.
Create a new Header capable of propagate the events to the correct instance of the two headers in the column.
I will go for 2, creating an header with two cell inside, you can use these two cell in the builder.
I have a tablecolumn with custom cell render this cell render takes an object and renders its properties as Labels. The problem is that I can't find a way to pass the same object in the arraylist to a column. Here is my code:
//I want to render this object in a column as well as use it in the rest of columns
CustomerCreationFlow cflow=new CustomerCreationFlow();
cflow.setId(10L);
cflow.setFirstName("Feras");
cflow.setLastName("Odeh");
cflow.setCustomerType("type");
ObservableList<CustomerCreationFlow> data = FXCollections.observableArrayList(cflow);
idclm.setCellValueFactory(new PropertyValueFactory<CustomerCreationFlow, String>("id"));
//I tried this but it didn't work
flowclm.setCellValueFactory(new PropertyValueFactory<CustomerCreationFlow, CustomerCreationFlow>("this"));
typeclm.setCellValueFactory(new PropertyValueFactory<CustomerCreationFlow, String>("customerType"));
flowTable.setItems(data);
Any Suggestion?
You should implement your custom CellFactory by extending TableCell.
In your custom TableCell, you can get the value of the line of the table (logically CustomerCreationFlow) by getting the TableRow of the current TableCell.
That gives:
class MyTableCell<S,T> extends TableCell<S, T>
#Override
public void updateItem(final T item, final boolean empty) {
super.updateItem(item, empty);
if (empty) {
this.setText(null);
this.setGraphic(null);
} else {
S item = (S) this.getTableRow().getItem();
// DO STUFF HERE
}
}
}
T is the type of the data defined by CellValueFactory. S is the type of the data representing a row.
For some reason, nothing changes about this JTable when this is called (this method updates the JTable after a user submits an SQL query).
Givens:
The dataVector and columnNamesVector are verified to be populated correctly.
JTable is a private class variable.
private void updateData() {
updateDataVariables();
table = new JTable(dataVector, columnNamesVector)
{
#SuppressWarnings({ "unchecked", "rawtypes" })
public Class getColumnClass(int column)
{
for (int row = 0; row < getRowCount(); row++)
{
Object o = getValueAt(row, column);
if (o != null)
{
return o.getClass();
}
}
return Object.class;
}
};
}
Any ideas?
It's a common beginner's fallacy to confuse objects with reference variables, but you need to understand that they are quite distinct. When you call this:
table = new JTable(dataVector, columnNamesVector) {.....
You are creating a new JTable object and having the table variable refer to it, but this has no effect on the JTable object that is displayed by the GUI, the one that the table variable was referring to previously. So you're changing the property of the reference variable, but leaving the original object unchanged.
The solution: You should not be creating a new JTable but rather you should be creating a new TableModel and then place that TableModel into the existing and visualized JTable. You can change a table's model by calling setModel(newModel) on it.
Edit: or as wolfcastle noted you could update the existing TableModel rather than create one anew.
You need to tell JTable that the data in model was updated with firing appropriate event.
See this tutorial
I have a JTable which uses a custom TableModel to display a series of object instances. There's a switch case inside getValueAt(int row, int column) to return values according to given attributes (see below). One return statement involves returning a value of 1/0 as true/false.
Is there a way that I can modify this TableModel so that it displays a 1/0 when a cell is edited?
public Object getValueAt(int row, int column) {
User user = (User)dataVector.get(row);
switch (column) {
case ID_INDEX:
return user.getId();
case USERNAME_INDEX:
return user.getUserName();
case PASSWORD_INDEX:
return "****";
case ACTIVATED_INDEX:
return (user.getActivated())?"true":"false";
default:
return new Object();
}
}
The default renderer and editor for Boolean is a JCheckBox. Consider using
case ACTIVATED_INDEX:
return Boolean.valueOf(user.getActivated());
Alternatively,
case ACTIVATED_INDEX:
return (user.getActivated())?"1":"0";
Addendum: As an example, DefaultTableModel does not override getColumnClass(), and AbstractTableModel simply returns Object.class. Your TableModel should override getColumnClass() accordingly:
DefaultTableModel dtm = new DefaultTableModel() {
#Override
public Class<?> getColumnClass(int col) {
return getValueAt(0, col).getClass();
}
};
// add some data
JTable table = new JTable(dtm);
You need to have a look at TableCellRenderer and TableCellEditor:
A TableCellRenderer is responsible for rendering cell data when it is not being edited, where as a TableCellEditor is responsible for providing a component used to edit the value of a cell. You can therefore represent the data in two separate ways depending on whether it is being edited or just rendered as per normal.
You should however consider that if you return a Boolean type from the getValueAt() method, your JTable should automatically render a JCheckBox, when the cell is in edit mode, the JCheckBox value can be changed by clicking on it as usual. To do this just return:
case ACTIVATED_INDEX:
return Boolean.valueOf(user.getActivated());