Limiting number of selected rows in Vaadin Table - java

In one of our applications we use the lazy query container to browse a potentially very large dataset. This works great. However, it is possible to select an arbitrary number of rows when using a multi-select table.
In our case, this can lead to selecting up to 500.000 rows (Vaadin limit) and then crash the VM.
Is there a way to limit the number of selected rows?
Here is a sample that shows the problem:
public class UIImpl extends UI {
private int SIZE = 500000;
#Override
protected void init(VaadinRequest request) {
// add a large table
LazyQueryContainer lqc = new LazyQueryContainer(
new QueryFactory() {
public Query constructQuery(QueryDefinition qd) {
return new Query() {
#Override
public int size() {
return SIZE;
}
#Override
public void saveItems(List<Item> addedItems, List<Item> modifiedItems, List<Item> removedItems) { }
#Override
public List<Item> loadItems(int startIndex, int count) {
List<Item> r = new ArrayList<>(count);
for (int i = startIndex; i<startIndex+count;i++) {
PropertysetItem item = new PropertysetItem();
item.addItemProperty("name", new ObjectProperty(i));
r.add(item);
}
return r;
}
#Override
public boolean deleteAllItems() {
return false;
}
#Override
public Item constructItem() {
return null;
}
};
}
},
null,
20,
false
);
lqc.addContainerProperty("name", Integer.class, null);
Table table = new Table();
table.setContainerDataSource(lqc);
table.setMultiSelect(true);
table.setSelectable(true);
table.setImmediate(true);
table.setVisibleColumns("name");
table.setSizeFull();
table.addValueChangeListener(new Property.ValueChangeListener() {
public void valueChange(Property.ValueChangeEvent event) {
System.err.println(event.getProperty().getValue());
}
});
setContent(table);
}
}

If you want to limit the number of rows a user is able to select you can use something similar to the following code:
public class TableWithSelectionLimit extends Table {
private final int maxSelections= -1;
private String[] lastSelected;
public TableWithSelectionLimit(int maxSelections) {
this.maxSelections = maxSelections;
}
#Override
public void changeVariables(Object source, Map<String, Object> variables) {
String[] selected = (String[]) variables.get("selected");
if (selected != null && selected.length > maxSelections) {
if (lastSelected != null) {
variables.put("selected", lastSelected);
} else {
variables.remove("selected");
}
markAsDirty();
} else {
lastSelected = selected;
}
super.changeVariables(source, variables);
}
}
This is of course optimizable, but it gives you an idea on how you could do it.
Update
For handling also selections produced using "Shift"+Click one has to handle/update these selection ranges additionally inside the method mentioned above.
Those can be retrieved using variables.get("selectedRanges") that will return a String[] containing items like "8-10" whereas the
first number is: the start index of the selection range
second number is: the amount of items selected starting at this index
Using this information it should be possible to update those values as wished and put them back into the variables using variables.put("selectedRanges", updatedRanges).
Attention: do not forget to call markAsDirty() if the values are changed, as otherwise the changes won't be propagated to the client side.

Related

Display data from Neo4j database into Java jtable

I have created a database in Neo4j and I have a query that displays two terms that occur together in the database. Now I can do this in Neo4j and it displays in a table for me but I am creating an application in java and I want to display it in a JTable. This is what I have so far:
public void dataList(){
ArrayList<Data> dataList = new ArrayList<>();
try {
// code
if(result.length>0)
{ for (int i = 0; i < results.length; i++)
{ neoQuery.set("query"+results[i]);
neoQuery.add ("query"); //etc
//Get Result like this
results[results.length-1]=recordVariable.get("recordVariable.get("recordNumbers").asInt();
}//end for each record
neoQuery.set("query"{+word1+"#"+word2+""})");
neoQuery.add("query"); //etc
//and result for strings which I want in another column like this
resultString = session.run(neoQuery.get()).next().get("result").asString();
Data data;
while(resultVariable.next() != null){
data = new Data((ResultSet) resultVariable).getInt("c4"), ((ResultSet) resultVariable).getString("w4"));
dataList.add(data);
}
}
}
}
} catch (Exception e){
System.out.println(e);
}
}
I also have another class for how I want to display the data. This is the code.
public class Data {
private int c4;
private String w4;
public Data(int c4, String w4)
{
this.c4=c4;
this.w4=w4;
}
public int getc4() {
return c4;
}
public String getw4() {
return w4;
}
So now that I have this code I want to display the data in two columns with unlimited rows. So far I have tried this.
public void show_data() {
ArrayList<Data> list = dataList();
DefaultTableModel model = (DefaultTableModel) jTable_Display_Data.getModel();
I have done research but just find resources for SQL. Now I can do this for a limited number of rows but I am not so sure how to write the rest for an unlimited number of rows. How can I do this for Neo4j?

getting ReadOnlyObjectWrapper value to a label

I have been think for somedays how to fix this but i need someone’s help.
I have a 2 tableviews and one is fill up with data. and if customer clicked on a row it gets it to other tableview. but the problem is with total price which supposed to dynamical add any item pick by a user.
I got this code and is perfect but I can’t seem to get the total to label and not in a row.
model
private final ReadOnlyObjectWrapper<Double> total = new ReadOnlyObjectWrapper<>();
public LineItemProperty(String name, Integer quantity, Double unitPrice) {
setName(name);
setQuantity(quantity);
setUnitPrice(unitPrice);
// Obvious binding for the total of this line item:
// total = quantity * unit price
total.bind(Bindings.createObjectBinding(new Callable<Double>() {
#Override
public Double call() throws Exception {
if (quantityProperty().get() == null || unitPriceProperty().get() == null) {
return 0.0;
}else{
return quantityProperty().get() * unitPriceProperty().get();
}
}
}, quantityProperty(), unitPriceProperty()));
}
in view
table.getColumns().add(column("Total Price", LineItemProperty::totalProperty, false, null));
final Label selected = new Label("Total");
another class for total
public class LineItemListWithTotal extends TransformationList<LineItemProperty, LineItemProperty>{
private final TotalLine totalLine;
public LineItemListWithTotal(
ObservableList<? extends LineItemProperty> source) {
super(source);
totalLine = new TotalLine(source);
}
#Override
protected void sourceChanged(ListChangeListener.Change<? extends LineItemProperty> c) {
// no need to modify change:
// indexes generated by the source list will match indexes in this
// list
fireChange(c);
}
Thanx a lot. any idea will help and appreciated

TableView doesn't refresh

I've got a project written in JavaFX and I'm trying to get a refresh on a tableview without result.
I've googled around and tried some examples I've found but it still doesn't work.
I populate a tableview with information each row in this table can have new comments added to by double click on the row. The a new Tabpane is opened and the new comment can be added there. On close of this tabpane I'd like the one I clicked from to be refreshed.
I must be doing something wrong. I just don't know what.
In my StoreController
private void populateTableView(List<Store> stores) {
ObservableList<Store> data = FXCollections.observableArrayList(stores);
storeNumberColumn.setCellValueFactory(
new PropertyValueFactory<Store, String>("id"));
storePhoneColumn.setCellValueFactory(
new PropertyValueFactory<Store, String>("phoneNbr"));
chainColumn.setCellValueFactory(
new PropertyValueFactory<Store, String>("chainId"));
commentColumn.setCellValueFactory(new Callback<TableColumn.CellDataFeatures<Store, ImageView>, ObservableValue<String>>() {
#Override
public ObservableValue<String> call(TableColumn.CellDataFeatures<Store, ImageView> p) {
Integer numberOfComments = p.getValue().getCommentsCount();
ReadOnlyObjectWrapper wrapper = null;
if (numberOfComments == 0) {
wrapper = null;
} else if (numberOfComments == 1) {
wrapper = new ReadOnlyObjectWrapper(new ImageView(COMMENT_SINGLE_FLAG_SOURCE));
} else {
wrapper = new ReadOnlyObjectWrapper(new ImageView(COMMENT_DOUBLE_FLAG_SOURCE));
}
return wrapper;
}
});
storeTable.setItems(data);
sortTable(storeTable, missedColumn);
}
#FXML
public void handleTableAction(MouseEvent event) {
if (event.getClickCount() == 2) {
showNewCommentStage();
}
}
private void showNewCommentStage() {
initCommentController();
Store store
= storeTable.getSelectionModel().selectedItemProperty().getValue();
commentController.showNewStage(commentPane, store);
}
It seems like the call-function doesn't get called when the commentpane is closed.
CommentController
public void showNewStage(Pane pane, Store store) {
this.store = store;
initStage(pane);
windowHandler = new WindowHandler(stage);
effectHandler.playEffect(pane);
constructCommentHeaders();
List<Comment> comments;
comments = commentService.listByStoreId(store.getId());
populateCommentTable(comments);
}
Like I said I've tried a lot of the solutions found here on Stackoverflow but with no results. The Tableview doesn't refresh. The Stores and the Comments are in different database tables if that's important
Can someone point me in the right direction?
Thanks!
****EDIT****
The Store.class
public class Store extends CommentEntity {
private String id;
private String chainId;
private String phoneNbr;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getChainId() {
return chainId;
}
public void setChainId(String chainId) {
this.chainId = chainId;
}
public String getPhoneNbr() {
return phoneNbr;
}
public void setPhoneNbr(String phoneNbr) {
this.phoneNbr = phoneNbr;
}
#Override
public String toString() {
return "Store{" + "id=" + id + ", chainId=" + chainId + '}';
}
#Override
public String getCommentIdentifier() {
return id;
}
}
The CommentEntity.Class
public abstract class CommentEntity {
private int commentsCount;
public int getCommentsCount() {
return commentsCount;
}
public void setCommentsCount(int commentsCount) {
this.commentsCount = commentsCount;
}
public abstract String getCommentIdentifier();
}
Thank you for input, I hadn't even reflected over the ImageView / String.
Two issues:
First, you need to distinguish between the data the cells in your column are displaying, and the cells that actually display those data. The cellValueFactory determines the data that are displayed. The PropertyValueFactory is a cellValueFactory implementation that references a JavaFX Property, so when you call
storeNumberColumn.setCellValueFactory(new PropertyValueFactory<Store, String>("id"));
it effectively tells the cells in the storeNumberColumn to call the idProperty() method on the Store object in the current row to get the data for the cell. (If no such method exists, it will try to use getId() as a backup plan.)
By default, you get a cellFactory that displays text resulting from calling toString() on the data generated by the cellValueFactory. In the case where your data are simply Strings, this is usually what you need. In other cases, you often need to provide a cellFactory of your own to get the correct way to display the data.
In your case, the data for the commentColumn are simply the number of comments. You are going to display that by choosing an image based on that numeric value.
So you should have
TableColumn<Store, Number> commentColumn = new TableColumn<>("Comments");
For the cellValueFactory, you can just use
commentColumn.setCellValueFactory(new PropertyValueFactory<>("commentsCount"));
Then you need a cellFactory that displays the appropriate ImageView:
commentColumn.setCellFactory(new Callback<TableColumn<Store, Number>, new TableCell<Store, Number>>() {
#Override
public TableCell<Store, Number>() {
private ImageView imageView = new ImageView();
#Override
public void updateItem(Number numberOfComments, boolean empty) {
super.updateItem(count, empty) ;
if (empty) {
setGraphic(null);
} else {
if (numberOfComments.intValue() == 0) {
setGraphic(null);
} else if (numberOfComments.intValue() == 1) {
imageView.setImage(new Image(COMMENT_SINGLE_FLAG_SOURCE));
setGraphic(imageView);
} else {
imageView.setImage(new Image(COMMENT_DOUBLE_FLAG_SOURCE));
setGraphic(imageView);
}
}
}
}
});
The second issue is actually about the update. A TableView keeps its contents "live" by observing JavaFX properties that are provided by the cellValueFactory as ObservableValues. If the value might change while the table is displayed, you must provide an actual property that can be observed: using a ReadOnlyObjectWrapper is no good (because it's read only, so it's wrapped value will not change). The PropertyValueFactory will also return a ReadOnlyObjectWrapper if you do not have JavaFX property accessor methods (i.e. if it is only using getXXX() methods to access the data). So your model class must provide JavaFX Properties.
You can make an immediate fix to this by updating CommentEntity to use an IntegerProperty:
public abstract class CommentEntity {
private final IntegerProperty commentsCount = new SimpleIntegerProperty();
public final int getCommentsCount() {
return commentsCountProperty().get();
}
public final void setCommentsCount(int commentsCount) {
commentsCountProperty().set(commentsCount);
}
public IntegerProperty commensCountProperty() {
return commentsCount ;
}
public abstract String getCommentIdentifier();
}
I would also strongly recommend updating the Store class to use JavaFX Properties in a similar manner.

Not able to sort CellTable Column

Trying to make my CellTable Colum sortable but I'm not getting it to work. I'm having an MVP application which gets data from a rest service. To show the data within the table works fine but to sort is doesn't work.
public class LicenseUsageUserViewImpl<T> extends Composite implements LicenseUsageUserView<T> {
#UiTemplate("LicenseUsageUserView.ui.xml")
interface LicenseDataViewUiBinder extends UiBinder<ScrollPanel,LicenseUsageUserViewImpl> {}
private static LicenseDataViewUiBinder uiBinder = GWT.create(LicenseDataViewUiBinder.class);
#UiField
CellTable<GWTLicenseUser> licenseUserCellTable;
List<GWTLicenseUser> licenseUsers;
ListDataProvider<GWTLicenseUser> dataProvider;
public List<GWTLicenseUser> getLicenseUsers() {
return licenseUsers;
}
public void setLicenseUsers(List<GWTLicenseUser> licenseUsers) {
this.licenseUsers = licenseUsers;
}
#UiField Label header;
ListHandler<GWTLicenseUser> sortHandler;
public LicenseUsageUserViewImpl() {
initWidget(uiBinder.createAndBindUi(this));
initCellTable();
}
#Override
public void setLicenseUsersTable(List<GWTLicenseUser> tmpLicenseUsers) {
if (tmpLicenseUsers.isEmpty()) {
licenseUserCellTable.setVisible(false);
} else {
setLicenseUsers(tmpLicenseUsers);
licenseUserCellTable.setWidth("100%");
licenseUserCellTable.setVisible(true);
licenseUserCellTable.setPageSize(getLicenseUsers().size());
licenseUserCellTable.setRowCount(getLicenseUsers().size(), false);
licenseUserCellTable.setRowData(0, getLicenseUsers());
licenseUserCellTable.setVisibleRange(new Range(0, licenseUserCellTable.getRowCount()));
sortHandler.setList(getLicenseUsers());
dataProvider.getList().clear();
dataProvider.getList().addAll(getLicenseUsers());
}
}
#Override
public void initCellTable() {
sortHandler = new ListHandler<GWTLicenseUser>(getLicenseUsers());
licenseUserCellTable.addColumnSortHandler(sortHandler);
licenseUserCellTable.setWidth("100%");
licenseUserCellTable.setVisible(true);
licenseUserCellTable.setVisibleRange(new Range(0, licenseUserCellTable.getRowCount()));
// Create a data provider.
dataProvider = new ListDataProvider<GWTLicenseUser>();
// Connect the table to the data provider.
dataProvider.addDataDisplay(licenseUserCellTable);
licenseUserCellTable.setWidth("100%");
licenseUserCellTable.setAutoHeaderRefreshDisabled(true);
licenseUserCellTable.setAutoFooterRefreshDisabled(true);
// userID
TextColumn<GWTLicenseUser> userIdColumn = new TextColumn<GWTLicenseUser>() {
#Override
public String getValue(GWTLicenseUser object) {
if (object != null ){
return object.getUserId();
} else {
return "NULL";
}
}
};
userIdColumn.setSortable(true);
sortHandler.setComparator(userIdColumn, new Comparator<GWTLicenseUser>() {
#Override
public int compare(GWTLicenseUser o1, GWTLicenseUser o2) {
return o1.getUserId().compareTo(o2.getUserId());
}
});
licenseUserCellTable.addColumn(userIdColumn, "User ID");
// more column entries
licenseUserCellTable.getColumnSortList().push(userIdColumn);
licenseUserCellTable.getColumnSortList().push(countColumn);
licenseUserCellTable.addColumnSortHandler(sortHandler);
}
}
setLicenseUsersTable is called from my activity with the response list of my users. When I start my application and make a rest call my data is provide and put into my list also shown within the CellTable but its not sortable, but I have this sort icon before my colum name. I figured I post the whole code because I think its know easier to see what I'm trying to do.
Thanks for any help.
Remove this line:
sortHandler.setList(getLicenseUsers());
You already passed a List into the SortHandler constructor in
sortHandler = new ListHandler<GWTLicenseUser>(getLicenseUsers());
Also, instead of
setLicenseUsers(tmpLicenseUsers);
you may need to use
licenseUsers.addAll(tmpLicenseUsers);
I hope one of them fixes the problem.

Making an addRows() method for a Custom JTable TableModel

My explanation below rambles, boiling down, is there a way I can add a Row without firing off an event, such that I can add multiple rows and fire an event to update all of them at once? Without having to add code to contain the table data in the custom model?
I have a custom TableModel which extends from DefaultTableModel so that I can use DefaultTableModel to keep track of data for me, whilst still having some custom methods of my own.
The issue is, I was thinking it might be faster for me to have an "addRows(String[][] val)" method, when I wish to add multiple rows. I could then fire a single event, probably fireTableDataChanged() to update the rows all at once. For example, my current method:
JTable table1 = new JTable(new dgvTableModel(new String[] {<values>},0, new String[] {<values>}));
table1.addRow(new String[] {<values here>});
table1.addRow(new String[] {<values here>});
table1.addRow(new String[] {<values here>});
I would then repeat the above as many times as necessary. The issue is, each of those will fire off a seperate event. It would be much faster (I think), if I could do this using my custom table model:
JTable table1 = new JTable(new dgvTableModel(new String[] {<values>},0, new String[] {<values>}));
table1.addRows(new String[][] {{<values1 here}, {values2 here}, . . .}});
and then in the table model:
public void addRows(String[][] values) {
for (String[] vals : values)
super.addRow(vals);
}
fireTableDataChanged();
}
I can code this in easily. The issue is again, that "super.addRow(vals);" line will fire an event each time through. Is there a way, without adding code to have my model contain the table data itself, to prevent that event being fired each time I add a row? Such that it waits for the fireTableDataChanged() call in the addRows method?
For reference, the code for my custom table model:
import java.awt.Color;
import java.util.ArrayList;
import javax.swing.UIManager;
import javax.swing.table.DefaultTableModel;
public class dgvTableModel extends DefaultTableModel {
//private DataTable tableVals = new DataTable();
private ArrayList<Color> rowColors;
//private ArrayList<Object[]> data = new ArrayList<>();
//default constructor has no data to begin with.
private int[] editableColumnNames;
public dgvTableModel(String[] colNames, int rowCount)
{
super(colNames, rowCount);
}
public dgvTableModel(String[] colNames, int rowCount, String[] editableColNames)
{
super(colNames, rowCount);
//this.tableVals.setColNames(colNames);
if (editableColNames!=null && editableColNames.length >0)
{
editableColumnNames = new int[editableColNames.length];
int count = 0;
for (int i =0; i< editableColNames.length;i++)
{
for (String val : colNames)
{
if (val.equalsIgnoreCase(editableColNames[i]))
{
editableColumnNames[count] = i;
count+=1;
break;
}
}
}
}
}
public dgvTableModel(String[] colNames, int rowCount, String[] editableColNames, boolean colorChanges)
{
super(colNames, rowCount);
Color defColor = UIManager.getDefaults().getColor("Table.background");
if (editableColNames!=null && editableColNames.length >0)
{
editableColumnNames = new int[editableColNames.length];
int count = 0;
if (colorChanges)
{
rowColors = new ArrayList<>();
}
for (int i =0; i< colNames.length;i++)
{
if (colorChanges)
{
rowColors.add(defColor);
}
for (String val : editableColNames)
{
if (val.equalsIgnoreCase(colNames[i]))
{
editableColumnNames[count] = i;
count+=1;
break;
}
}
}
}
else if (colorChanges)
{
rowColors = new ArrayList<>();
for (String val : colNames)
{
rowColors.add(defColor);
}
}
}
#Override
public boolean isCellEditable(int row, int column)
{
if(editableColumnNames!=null && editableColumnNames.length >0)
{
for (int colID : editableColumnNames)
{
if (column==colID)
return true;
}
}
return false;
}
public void setRowColor(int row, Color c)
{
rowColors.set(row, c);
fireTableRowsUpdated(row,row);
}
public Color getRowColor(int row)
{
return rowColors.get(row);
}
#Override
public Class getColumnClass(int column)
{
return String.class;
}
#Override
public String getValueAt(int row, int column)
{
return super.getValueAt(row, column).toString();
}
}
Surely firing one event to display every row, is faster than firing one event for each row?
'AbstractTableModel.fireTableDataChanged()' is used to indicate to the model (and the JTable UI which is notified by the model) that all possible data in the table may have changed and needs to be checked. This can (with emphasis on can) be an expensive operation. If you know which rows have been added, just use the 'AbstractTableModel.fireTableRowsInserted(int firstRow, int lastRow)' method instead. This will ensure only the effect rows are seen as changed. Take a look at all the fire* methods in AbstractTableModel. You can really exercise fine grained control over which rows, cells, etc are seen as dirty.
Then again what your doing might be premature optimalization. Unless you have fiftythousand records in your JTable this is probably not going to be noticable. But if you have a massive amount of records in your JTable you might be beter of lazy loading them anyway.

Categories