The question was asked here: https://vaadin.com/forum/thread/18095407/how-to-create-a-grid-without-binder
However the vaadin's forum closed so i want to continue it here.
On Vaadin 14, Any recommendation on the best way to implement grid with dynamic varying number of columns. Using column Index (1,2,3...) is not a good choice for me. Let say I have a simple Json file (only 1 level: key-value) to map to a grid and this Json has an unknown list of properties.
which approach is better in term of performance ?:
[Option 1]
class Data {
private Map<String, Object> values = new HashMap<>();
public void set(String key, Object val) {
values.put(key, val);
}
public Object get(String key) {
return values.get(key);
}
}
Grid<Data> myGrid = new Grid<>();
[Option 2]
public class GridDynamicValueProvider implements ValueProvider<GridDynamicRow, Object> {
private int columnIndex;
public GridDynamicValueProvider(int columnIndex) {
this.columnIndex = columnIndex;
}
#Override
public Object apply(GridDynamicRow dynamicRow) {
return dynamicRow.getValue(columnIndex);
}
}
public class GridDynamicRow {
private List<Object> values = new ArrayList<>();
public void addValue(String value) {
values.add(value);
}
public Object getValue(int columnIndex) {
return values.get(columnIndex);
}
}
The SerializablePredicate of Vaadin accepts both function references and Lambdas, thus it is possible to use non-POJO data types with Grid and Binder in Vaadin, although that is a bit unconventional. The key ingredients are:
Grid<Map<String, Integer>> grid = new Grid<>();
...
grid.addColumn(map -> map.get("column")).setHeader("Column");
You can also wrap the Map in custom class if you have need to protect the internals.
class Data {
private Map<String, Object> values = new HashMap<>();
public void set(String key, Object val) {
values.put(key, val);
}
public Object get(String key) {
return values.get(key);
}
}
Grid<Data> myGrid = new Grid<>();
As for the performance, essentially, you're comparing between using a List where you fetch by index versus a HashMap where you fetch by key. Here's a related question: ArrayList .get faster than HashMap .get?
You can use also ArrayList as Grid's type if you can index the columns with a number.
The both approaches allow generating Grid's with varying dynamic number of columns, for example if you read the data directly from a file or have raw data backend queries.
Related
we are trying to submit a node using the integrated extension function. The node looks correct as far as it goes, but we can't access the individual elements, because there is always an outOfBound exception appearance.
How can we access the individual elements below the root element?
public ExtensionFunction updateTempNode = new ExtensionFunction() {
public QName getName() {
return new QName("de.dkl.dymoServer.util.ExternalFunctions", "updateTempNode");
}
public SequenceType getResultType() {
return SequenceType.makeSequenceType(
ItemType.BOOLEAN, OccurrenceIndicator.ONE
);
}
public net.sf.saxon.s9api.SequenceType[] getArgumentTypes() {
return new SequenceType[]{
SequenceType.makeSequenceType(
ItemType.STRING, OccurrenceIndicator.ONE),
SequenceType.makeSequenceType(
ItemType.DOCUMENT_NODE, OccurrenceIndicator.ONE)};
}
public XdmValue call(XdmValue[] arguments) {
String sessionId = arguments[0].itemAt(0).getStringValue();
SaplingElement tempNode = TransformationService.tempNodes.get(sessionId);
ItemTypeFactory itemTypeFactory = new ItemTypeFactory(((XdmNode) arguments[1]).getProcessor());
tempNode.withChild(
arguments[1].stream().map(xdmValue -> Saplings.elem(xdmValue.getStringValue()).withText(xdmValue.itemAt(0).getStringValue())).toList()
.toArray(SaplingElement[]::new)
);
System.out.println(tempNode);
return new XdmAtomicValue(true);
}
};
AOOB as I try to iterate
Data expected as document_node
Wild guess is that you want something like
tempNode = tempNode.withChild(
arguments[1]
.select(Steps.child().then(Steps.child()))
.map(childNode -> Saplings.elem(childNode.getNodeName()).withText(childNode.itemAt(0).getStringValue()))
.collect(Collectors.toList())
.toArray(new SaplingElement[]{})
);
which would populate tempNode with copies of the child nodes of the root element of the document node that is arguments[1]. There might be better ways to do that.
.
I have not found how to save a general list of primitive types, e.g. ints, or strings, in a property of an entity. I might have missed something obvious...
https://github.com/JetBrains/xodus/wiki/Entity-Stores described that "only Java primitive types, Strings, and ComparableSet values can be used by default".
It seems not hard to convert an Iterable into a ComparableSet. However it is a Set.
I will take a look into PersistentEntityStore.registerCustomPropertyType() to see if that helps. I just feel wrong to do that to just save a list of integers.
Links seemed to be able to serve as a way of saving a list of Entitys. But it seems there is no addProperty() counterpart to addLink().
Appreciated if some one can share a way or a workaround for this, or maybe why this is not supported.
Thanks
As mentioned in the comments, one workaround I came up with was to create a ComparableList, by adopting code from ComparableSet.
The idea was to make a list that is able to convert to and from an ArrayByteIterable, and register it with .registerCustomPropertyType(). To do that, 2 classes are needed, ComparableList and ComparableListBinding. I'm sharing one iteration I used at the bottom. By the way, I made them immutable, comparing to the mutable ComparableSet. The newly implemented types should be registered once in a transaction of the store before using them.
That should allow you to store and retrieve a list. However the items in ComparableList would not get indexed as they would when saved in a ComparableSet -- there are some special treatment for ComparableSet in the entity store implementation. So without modifying the library, indexing would work only with hacks like creating another property to just index the values.
I was considering to implement a different entity store that could better support lists, on top of the xodus key-value store, bypassing the xodus entity store entirely. That might be a better solution to the list issue we are talking about here.
ComparableList:
#SuppressWarnings("unchecked")
public class ComparableList<T extends Comparable<T>> implements Comparable<ComparableList<T>>,
Iterable<T> {
#Nonnull
private final ImmutableList<T> list;
public ComparableList(#Nonnull final Iterable<T> iterable) {
list = ImmutableList.copyOf(iterable);
}
#Override
public int compareTo(#Nonnull final ComparableList<T> other) {
final Iterator<T> thisIt = list.iterator();
final Iterator<T> otherIt = other.list.iterator();
while (thisIt.hasNext() && otherIt.hasNext()) {
final int cmp = thisIt.next().compareTo(otherIt.next());
if (cmp != 0) {
return cmp;
}
}
if (thisIt.hasNext()) {
return 1;
}
if (otherIt.hasNext()) {
return -1;
}
return 0;
}
#NotNull
#Override
public Iterator<T> iterator() {
return list.iterator();
}
#Nullable
public Class<T> getItemClass() {
final Iterator<T> it = list.iterator();
return it.hasNext() ? (Class<T>) it.next().getClass() : null;
}
#Override
public String toString() {
return list.toString();
}
}
ComparableListBinding:
#SuppressWarnings({"unchecked", "rawtypes"})
public class ComparableListBinding extends ComparableBinding {
public static final ComparableListBinding INSTANCE = new ComparableListBinding();
private ComparableListBinding() {}
#Override
public ComparableList readObject(#NotNull final ByteArrayInputStream stream) {
final int valueTypeId = stream.read() ^ 0x80;
final ComparableBinding itemBinding = ComparableValueType.getPredefinedBinding(valueTypeId);
final ImmutableList.Builder<Comparable> builder = ImmutableList.builder();
while (stream.available() > 0) {
builder.add(itemBinding.readObject(stream));
}
return new ComparableList(builder.build());
}
#Override
public void writeObject(#NotNull final LightOutputStream output,
#NotNull final Comparable object) {
final ComparableList<? extends Comparable> list = (ComparableList) object;
final Class itemClass = list.getItemClass();
if (itemClass == null) {
throw new ExodusException("Attempt to write empty ComparableList");
}
final ComparableValueType type = ComparableValueType.getPredefinedType(itemClass);
output.writeByte(type.getTypeId());
final ComparableBinding itemBinding = type.getBinding();
list.forEach(o -> itemBinding.writeObject(output, o));
}
/**
* De-serializes {#linkplain ByteIterable} entry to a {#code ComparableList} value.
*
* #param entry {#linkplain ByteIterable} instance
* #return de-serialized value
*/
public static ComparableList entryToComparableList(#NotNull final ByteIterable entry) {
return (ComparableList) INSTANCE.entryToObject(entry);
}
/**
* Serializes {#code ComparableList} value to the {#linkplain ArrayByteIterable} entry.
*
* #param object value to serialize
* #return {#linkplain ArrayByteIterable} entry
*/
public static ArrayByteIterable comparableSetToEntry(#NotNull final ComparableList object) {
return INSTANCE.objectToEntry(object);
}
}
Let's consider we have the following informations :
As you see an article can be stored in many stores, and vice versa : a store can store many articles : that's the class model (UML )
some code :
FXML Part :
#FXML
private TableView<Article> tblArticles;
#FXML
private TableColumn<Article, String> colStore;
#FXML
private TableColumn<Article, Integer> colQuantity;
getters and setter :
colStore.setCellValueFactory(new PropertyValueFactory<>("store"));
colStore.setCellValueFactory(new PropertyValueFactory<>("quantity"));
I recieve the result seen in the first table but I am not able to do what is in the second table .
And what I want exactly should give the following informations :
So my question is it possible to do this in a TableView ?
Here is a sample app. It follows an MVVM style, which is appropriate for this kind of work. The app was built using Java 13 and will not work in earlier Java versions such as Java 8. It's a relatively long answer, but, ah well, sometimes that is what it takes.
The overall approach is not to create a tableview row for each store that an article is stored in. Instead, we just create a row for each article and we have a custom cell renderer which produces a single formatted cell for all the stores and quantities that that item is stored at.
Now, you could do an alternative implementation based upon a custom rowFactory. However, I do not recommend that approach for this particular task, as I believe it would be unnecessarily complicated to implement and maintain, without providing sufficient value.
Another way to do this is to use nested columns. This approach, when appropriate care is taken, does allow you to create a tableview row for each store that an article is stored in. If you do this, you need some way of populating different data depending on whether a row is either the first row in the group or not. You don't allow the user to reorder and sort data in the table, as that would be quite difficult to cater for because the notion of what is the "first row in the group" would be forever changing. To allow for appropriate rendering with nested columns, you end up with a slightly different view model (the FlatLineItem class below and the accompanying method in the LineItemService that retrieves them).
The image below demonstrates the output of a TableView with a custom cell renderer on the left and a TableView using nested columns on the right. Note how the selection works differently in each case. On the left when a row is selected, it includes all the stores that attached to that row. On the right when the nested columns are used, the row selection is only selecting a row for a given store.
Main application class
This sets up a couple of TableViews.
For the first table view, all it does is create a TableView with a column for each of the elements to be displayed. All the data is extracted from a LineItem view model class using a standard PropertyValueFactory. The slightly different thing is a custom cell renderer for a StoredQuantity field via the StoredQuantityTableCell, this is explained later.
The second view uses nested columns and works based upon the FlatLineItem view model class, also using a standard PropertyValueFactory and uses no custom cell renderer.
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.scene.layout.HBox;
import javafx.stage.Stage;
import java.util.List;
public class AggregateViewApp extends Application {
#Override
public void start(Stage stage) throws Exception {
LineItemService lineItemService = new LineItemService();
TableView<LineItem> tableView = createArticleTableView();
tableView.getItems().setAll(lineItemService.fetchAllLineItems());
TableView<FlatLineItem> nestedTableView = createNestedArticleTableView();
nestedTableView.getItems().setAll(lineItemService.fetchAllFlatLineItems());
HBox layout = new HBox(
40,
tableView,
nestedTableView
);
stage.setScene(new Scene(layout));
stage.show();
}
#SuppressWarnings("unchecked")
private TableView<LineItem> createArticleTableView() {
TableView tableView = new TableView();
TableColumn<LineItem, Long> articleIdCol = new TableColumn<>("Article ID");
articleIdCol.setCellValueFactory(new PropertyValueFactory<>("articleId"));
TableColumn<LineItem, String> nameCol = new TableColumn<>("Name");
nameCol.setCellValueFactory(new PropertyValueFactory<>("articleName"));
TableColumn<LineItem, List<StoredQuantity>> storedArticleCol = new TableColumn<>("Store Quantities");
storedArticleCol.setCellValueFactory(new PropertyValueFactory<>("storedQuantities"));
storedArticleCol.setCellFactory(lineItemStringTableColumn -> new StoredQuantityTableCell());
TableColumn<LineItem, DB.StoredArticle> totalCol = new TableColumn<>("Total");
totalCol.setCellValueFactory(new PropertyValueFactory<>("total"));
tableView.getColumns().addAll(articleIdCol, nameCol, storedArticleCol, totalCol);
tableView.setPrefSize(400, 150);
return tableView;
}
#SuppressWarnings("unchecked")
private TableView<FlatLineItem> createNestedArticleTableView() {
TableView tableView = new TableView();
TableColumn<FlatLineItem, Long> articleIdCol = new TableColumn<>("Article ID");
articleIdCol.setCellValueFactory(new PropertyValueFactory<>("articleId"));
articleIdCol.setSortable(false);
TableColumn<FlatLineItem, String> nameCol = new TableColumn<>("Name");
nameCol.setCellValueFactory(new PropertyValueFactory<>("articleName"));
nameCol.setSortable(false);
TableColumn<FlatLineItem, String> storeCol = new TableColumn<>("Store");
storeCol.setCellValueFactory(new PropertyValueFactory<>("storeName"));
storeCol.setSortable(false);
TableColumn<FlatLineItem, String> storeQuantityCol = new TableColumn<>("Quantity");
storeQuantityCol.setCellValueFactory(new PropertyValueFactory<>("storeQuantity"));
storeQuantityCol.setSortable(false);
TableColumn<FlatLineItem, List<StoredQuantity>> storedArticleCol = new TableColumn<>("Store Quantities");
storedArticleCol.getColumns().setAll(
storeCol,
storeQuantityCol
);
storedArticleCol.setSortable(false);
TableColumn<LineItem, DB.StoredArticle> totalCol = new TableColumn<>("Total");
totalCol.setCellValueFactory(new PropertyValueFactory<>("total"));
totalCol.setSortable(false);
tableView.getColumns().setAll(articleIdCol, nameCol, storedArticleCol, totalCol);
tableView.setPrefSize(400, 200);
return tableView;
}
public static void main(String[] args) {
launch(AggregateViewApp.class);
}
}
StoredQuantityTableCell.java
This takes a list of StoredQuantities which is a tuple of a store name and a quantity of things stored at that store and then renders that list into a single cell, formatting the display internally in a GridView. You could use whatever internal node layout or formatting you wish and add CSS styling to spice things up if necessary.
import javafx.scene.control.Label;
import javafx.scene.control.TableCell;
import javafx.scene.layout.GridPane;
import java.util.List;
class StoredQuantityTableCell extends TableCell<LineItem, List<StoredQuantity>> {
private GridPane storedQuantityPane;
public StoredQuantityTableCell() {
storedQuantityPane = new GridPane();
storedQuantityPane.setHgap(10);
storedQuantityPane.setVgap(5);
}
#Override
protected void updateItem(List<StoredQuantity> storedQuantities, boolean empty) {
super.updateItem(storedQuantities, empty);
if (storedQuantities == null) {
setGraphic(null);
return;
}
storedQuantityPane.getChildren().removeAll(storedQuantityPane.getChildren());
int row = 0;
for (StoredQuantity storedQuantity: storedQuantities) {
storedQuantityPane.addRow(
row,
new Label(storedQuantity.getStoreName()),
new Label("" + storedQuantity.getQuantity())
);
row++;
}
setGraphic(storedQuantityPane);
}
}
LineItem.java
A view model class representing a row in the table.
import java.util.Collections;
import java.util.List;
public class LineItem {
private long articleId;
private String articleName;
private List<StoredQuantity> storedQuantities;
public LineItem(long articleId, String articleName, List<StoredQuantity> storedQuantities) {
this.articleId = articleId;
this.articleName = articleName;
this.storedQuantities = storedQuantities;
}
public long getArticleId() {
return articleId;
}
public String getArticleName() {
return articleName;
}
public List<StoredQuantity> getStoredQuantities() {
return Collections.unmodifiableList(storedQuantities);
}
public int getTotal() {
return storedQuantities.stream()
.mapToInt(StoredQuantity::getQuantity)
.sum();
}
}
StoredQuantity.java
A view model class representing a store name and quantity of things in the store. This is used by the StoredQuantityTableCell to render the stored quantities for a line item.
public class StoredQuantity implements Comparable<StoredQuantity> {
private String storeName;
private int quantity;
StoredQuantity(String storeName, int quantity) {
this.storeName = storeName;
this.quantity = quantity;
}
public String getStoreName() {
return storeName;
}
public int getQuantity() {
return quantity;
}
#Override
public int compareTo(StoredQuantity o) {
return storeName.compareTo(o.storeName);
}
}
FlatLineItem.java
A view model class supporting a table view with nested columns. A flat line item which can be created for each store that an article is stored in.
public class FlatLineItem {
private Long articleId;
private String articleName;
private final String storeName;
private final Integer storeQuantity;
private final Integer total;
private final boolean firstInGroup;
public FlatLineItem(Long articleId, String articleName, String storeName, Integer storeQuantity, Integer total, boolean firstInGroup) {
this.articleId = articleId;
this.articleName = articleName;
this.storeName = storeName;
this.storeQuantity = storeQuantity;
this.total = total;
this.firstInGroup = firstInGroup;
}
public Long getArticleId() {
return articleId;
}
public String getArticleName() {
return articleName;
}
public String getStoreName() {
return storeName;
}
public Integer getStoreQuantity() {
return storeQuantity;
}
public Integer getTotal() {
return total;
}
public boolean isFirstInGroup() {
return firstInGroup;
}
}
LineItemService.java
This translates values from the database into view model objects (LineItems or FlatLineItems) which can be rendered by the views. Note how the getFlatLineItemsForLineItem which constructs the FlatLineItems for the nested column table view has a notion of what it the first row in a group of line items and propagates the the FlatLineItem appropriately based on that, leaving some values null if they are just repeated from the first item in the group, which results in a clean display.
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
public class LineItemService {
private final DB db = DB.instance();
public List<LineItem> fetchAllLineItems() {
return db.findAllArticles()
.stream()
.map(article -> createLineItemForArticle(article.getArticleId()))
.collect(Collectors.toList());
}
public List<FlatLineItem> fetchAllFlatLineItems() {
return fetchAllLineItems().stream()
.flatMap(lineItem -> getFlatLineItemsForLineItem(lineItem).stream())
.collect(Collectors.toList());
}
private List<FlatLineItem> getFlatLineItemsForLineItem(LineItem lineItem) {
ArrayList<FlatLineItem> flatLineItems = new ArrayList<>();
boolean firstStore = true;
for (StoredQuantity storedQuantity: lineItem.getStoredQuantities()) {
FlatLineItem newFlatLineItem;
if (firstStore) {
newFlatLineItem = new FlatLineItem(
lineItem.getArticleId(),
lineItem.getArticleName(),
storedQuantity.getStoreName(),
storedQuantity.getQuantity(),
lineItem.getTotal(),
true
);
firstStore = false;
} else {
newFlatLineItem = new FlatLineItem(
null,
null,
storedQuantity.getStoreName(),
storedQuantity.getQuantity(),
null,
false
);
}
flatLineItems.add(newFlatLineItem);
}
return flatLineItems;
}
private LineItem createLineItemForArticle(long articleId) {
DB.Article article =
db.findArticleById(
articleId
).orElse(
new DB.Article(articleId, "N/A")
);
List<DB.StoredArticle> storedArticles =
db.findAllStoredArticlesForArticleId(articleId);
return new LineItem(
article.getArticleId(),
article.getName(),
getStoredQuantitesForStoredArticles(storedArticles)
);
}
private List<StoredQuantity> getStoredQuantitesForStoredArticles(List<DB.StoredArticle> storedArticles) {
return storedArticles.stream()
.map(storedArticle ->
new StoredQuantity(
db.findStoreById(storedArticle.getStoreId())
.map(DB.Store::getName)
.orElse("No Store"),
storedArticle.getQuantity()
)
)
.sorted()
.collect(
Collectors.toList()
);
}
}
Mock database class
Just a simple in-memory representation of the database class. In a real app, you would probably use something like SpringData with hibernate to provide the data access repositories using a JPA based object to relational mapping.
The database classes aren't related to the view at all but are just presented here so that a running app can be created within a MVVM style framework.
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
class DB {
private static final DB instance = new DB();
public static DB instance() {
return instance;
}
private List<Article> articles = List.of(
new Article(1, "Hp101"),
new Article(3, "Lenovo303"),
new Article(4, "Asus404")
);
private List<Store> stores = List.of(
new Store(1, "S1"),
new Store(2, "S2")
);
private List<StoredArticle> storedArticles = List.of(
new StoredArticle(1, 1, 30),
new StoredArticle(1, 2, 70),
new StoredArticle(3, 1, 50),
new StoredArticle(4, 2, 70)
);
public Optional<Article> findArticleById(long articleId) {
return articles.stream()
.filter(article -> article.getArticleId() == articleId)
.findFirst();
}
public Optional<Store> findStoreById(long storeId) {
return stores.stream()
.filter(store -> store.getStoreId() == storeId)
.findFirst();
}
public List<StoredArticle> findAllStoredArticlesForArticleId(long articleId) {
return storedArticles.stream()
.filter(storedArticle -> storedArticle.articleId == articleId)
.collect(Collectors.toList());
}
public List<Article> findAllArticles() {
return Collections.unmodifiableList(articles);
}
static class Article {
private long articleId;
private String name;
public Article(long articleId, String name) {
this.articleId = articleId;
this.name = name;
}
public long getArticleId() {
return articleId;
}
public String getName() {
return name;
}
}
static class Store {
private long storeId;
private String name;
public Store(long storeId, String name) {
this.storeId = storeId;
this.name = name;
}
public long getStoreId() {
return storeId;
}
public String getName() {
return name;
}
}
static class StoredArticle {
private long articleId;
private long storeId;
private int quantity;
public StoredArticle(long articleId, long storeId, int quantity) {
this.articleId = articleId;
this.storeId = storeId;
this.quantity = quantity;
}
public long getArticleId() {
return articleId;
}
public long getStoreId() {
return storeId;
}
public int getQuantity() {
return quantity;
}
}
}
Answers to some follow-up questions
Which Approach is the best for updating data ?
All of the approaches I have shown use read only data models and views. To make it read-write would be a bit more work (and out of scope for what I would be prepared to add to this already long answer). Probably, of the two approaches outlined above, the approach which uses a separate row for each store containing an item would be the easiest to adapt to making the data updatable.
Which approach in general I should use to update data ( data are stored for sure in db) ?
Defining a general approach to updating data in a database is out of scope for what I would answer here (it is a purely opinion based answer, as there are many different ways to accomplish this, and as such is off topic for StackOverflow). If it were me, I'd set up a SpringBoot based rest service that connected to the database and have my client app communicate with that. If the app does not need to communicate over the internet, but only communicate with a local DB over a LAN, then adding direct database access by making the app a SpringBoot app and using Spring Data repositories with the embedded H2 database is what I would use.
Is when modifying in a specific row modify in db or wait until user modify in the whole tableview and click on a save button ?
Either way would work, I don't have any strong opinion on one versus the other. I'd probably lean towards the immediate update scenario rather than a delayed save scenario, but it would depend on the app and desired user experience.
Please can you provide me with some code for either to draw a line under every cell or to make it just like usual tableView ( one row gray and one not etc ...)
You can ask that as a separate question. But, in general, use CSS styling. If you use the second approach outlined above which has a row per store, then everything is already a "usual tableView" in terms of styling, with one row gray and one row not, etc., so I don't know that any additional styling is really required in such a case.
I am working on a project which deal with heavy data sets. I am using Primefaces 4 & 5, spring and hibernate. I have to to display a very huge datasets such as min 3000 rows with 100 columns with various features such as sorting, filtering, row-expansion etc. My problem is, my applications took 8 to 10 mins to show the whole page as well as other functionalities(sorting, filtering ) also takes a lot time. My client is not happy at all. However I can use pagination for this but again My client do not want paging. So I decided to use livescroll but unfortunately I failed to implement livescroll with lazyload or without lazyload as there were bugs in PF regarding livescroll. also i have posted this question here earlier but no solution found.
This performance issue is very critical and show stopper for me. To show 3000 rows with 100 columns, the size of the page which is getting loaded is ~10MB.
I have calculated the time consumed by various life-cycles of of JSF, using Phase-listener I figure out that its Browser who is taking time to parse the response rendered by jsf. To complete the all phases my application took only 25 sec.
At minimal I want to increase the performance of my project. Please share any idea, suggestion and anything which could help to overcome this problem
Note: There is no database manipulations in getters and setters as well as no complex business logic.
UPDATE :
This is my datatable without lazyload:
<p:dataTable
style="width:100%"
id="cdTable"
selection="#{controller.selectedArray}"
resizableColumns="true"
draggableColumns="true"
var="cd"
value="#{controller.cdDataModel}"
editable="true"
editMode="cell"
selectionMode="multiple"
rowSelectMode="add"
scrollable="true"
scrollHeight="650"
rowKey="#{cd.id}"
rowIndexVar="rowIndex"
styleClass="screenScrollStyle"
liveScroll="true"
scrollRows="50"
filterEvent="enter"
widgetVar="dt4"
>
Here everything is working except filtering. Once I filter then first page is displayed but unable to sort or livescroll on datatable. Note this I have tested in Primefaces5.
2nd Approch
With lazyload with same datatable
1) When I add rows="100" livescroll happens but problem with row-editing, row-expansion but filter & sorting works.
2) When I remove rows livescroll works with row-editing, row-expansion etc but filter & sorting dont work.
My LazyLoadModel is as follows
public class MyDataModel extends LazyDataModel<YData>
{
#Override
public List<YData> load(int first, int pageSize,
List<SortMeta> multiSortMeta, Map<String, Object> filters) {
System.out.println("multisort wala load");
return super.load(first, pageSize, multiSortMeta, filters);
}
/**
*
*/
private static final long serialVersionUID = 1L;
private List<YData> datasource;
public YieldRecBondDataModel() {
}
public YieldRecBondDataModel(List<YData> datasource) {
this.datasource = datasource;
}
#Override
public YData getRowData(String rowKey) {
// In a real app, a more efficient way like a query by rowKey should be
// implemented to deal with huge data
// List<YData> yList = (List<YData>) getWrappedData();
for (YData y : datasource)
{
System.out.println("datasource :"+datasource.size());
if(y.getId()!=null)
{
if (y.getId()==(new Long(rowKey)))
{
return y;
}
}
}
return null;
}
#Override
public Object getRowKey(YData y) {
return y.getId();
}
#Override
public void setRowIndex(int rowIndex) {
/*
* The following is in ancestor (LazyDataModel):
* this.rowIndex = rowIndex == -1 ? rowIndex : (rowIndex % pageSize);
*/
if (rowIndex == -1 || getPageSize() == 0) {
super.setRowIndex(-1);
}
else
super.setRowIndex(rowIndex % getPageSize());
}
#Override
public List<YData> load(int first, int pageSize, String sortField, SortOrder sortOrder, Map<String,Object> filters) {
List<YData> data = new ArrayList<YData>();
System.out.println("sort order : "+sortOrder);
//filter
for(YData yInfo : datasource) {
boolean match = true;
for(Iterator<String> it = filters.keySet().iterator(); it.hasNext();) {
try {
String filterProperty = it.next();
String filterValue = String.valueOf(filters.get(filterProperty));
Field yField = yInfo.getClass().getDeclaredField(filterProperty);
yField.setAccessible(true);
String fieldValue = String.valueOf(yField.get(yInfo));
if(filterValue == null || fieldValue.startsWith(filterValue)) {
match = true;
}
else {
match = false;
break;
}
} catch(Exception e) {
e.printStackTrace();
match = false;
}
}
if(match) {
data.add(yInfo);
}
}
//sort
if(sortField != null) {
Collections.sort(data, new LazySorter(sortField, sortOrder));
}
int dataSize = data.size();
this.setRowCount(dataSize);
//paginate
if(dataSize > pageSize) {
try {
List<YData> subList = data.subList(first, first + pageSize);
return subList;
}
catch(IndexOutOfBoundsException e) {
return data.subList(first, first + (dataSize % pageSize));
}
}
else
return data;
}
#Override
public int getRowCount() {
// TODO Auto-generated method stub
return super.getRowCount();
}
}
I am fade up with these issues and becomes show stopper for me. Even i tried Primefaces 5
If your data is loaded from db i suggest you to do a better LazyDataModel like:
public class ElementiLazyDataModel extends LazyDataModel<T> implements Serializable {
private Service<T> abstractFacade;
public ElementiLazyDataModel(Service<T> abstractFacade) {
this.abstractFacade = abstractFacade;
}
public Service<T> getAbstractFacade() {
return abstractFacade;
}
public void setAbstractFacade(Service<T> abstractFacade) {
this.abstractFacade = abstractFacade;
}
#Override
public List<T> load(int first, int pageSize, String sortField, SortOrder sortOrder, Map<String, Object> filters) {
PaginatedResult<T> pr = abstractFacade.findRange(new int[]{first, first + pageSize}, sortField, sortOrder, filters);
setRowCount(new Long(pr.getTotalItems()).intValue());
return pr.getItems();
}
}
The service is some kind of backend communication (like an EJB) injected in the ManagedBean that use this model.
The service for pagination may be like this:
#Override
public PaginatedResult<T> findRange(int[] range, String sortField, SortOrder sortOrder, Map<String, Object> filters) {
final Query query = getEntityManager().createQuery("select x from " + entityClass.getSimpleName() + " x")
.setFirstResult(range[0]).setMaxResults(range[1] - range[0] + 1);
// Add filter sort etc.
final Query queryCount = getEntityManager().createQuery("select count(x) from " + entityClass.getSimpleName() + " x");
// Add filter sort etc.
Long rowCount = (Long) queryCount.getSingleResult();
List<T> resultList = query.getResultList();
return new PaginatedResult<T>(resultList, rowCount);
}
Note that you have to do the paginated query (with jpa like this the orm do the query for you, but if you don't use orm have to do paginated query, for oracle look at TOP-N query, for example: http://oracle-base.com/articles/misc/top-n-queries.php)
Remember your return obj must be contains also the total record as a fast count:
public class PaginatedResult<T> implements Serializable {
private List<T> items;
private long totalItems;
public PaginatedResult() {
}
public PaginatedResult(List<T> items, long totalItems) {
this.items = items;
this.totalItems = totalItems;
}
public List<T> getItems() {
return items;
}
public void setItems(List<T> items) {
this.items = items;
}
public long getTotalItems() {
return totalItems;
}
public void setTotalItems(long totalItems) {
this.totalItems = totalItems;
}
}
All this is useful if your database table is correctly setup, pay aptention to the execution plan of the possible query and add the right index.
Hope to give some hint to improve you performance
In the end, remember to your final user that the human eyes can't see more that 10-20 record at once, so it is very useless to have thousand record in a page.
You have used the default load implementation which is used in the showcases of Primefaces. This is not the correct implementation for your case where you load your data from a database.
The load method should use the correct query with consideration of :
1) the filter fields that are used, example:
String query = "select e from Entity e where lower(e.f1) like lower('" + filters.get(key) + "'%) and..., etc. for the other fields
2) the sorting columns that are used, example:
query.append("order by ").append(sortField).append(" ").append(SortOrder.ASCENDING.name() ? "" : sortOrder.substring(0, 4)),..., etc. for the other columns.
3) The total count of your query WITH 1) attached to it. Example:
Long totalCount = (Long) entityManager.createQuery("select count(*) from Entity e where lower(e.f1) like lower('filterKey1%') and lower(e.f2) like lower('filterKey2%') ...").getSingleResult();
In short: The comboboxes in each field of my table in editing mode is giving me a conversion error when selecting an item, but the same logic and containers are working perfectly well outside of the TableFieldFactory (createField()). What am I doing wrong?
Longer explanation:
I have a container with multiple properties (columns) and items (rows). When I edit the table that is connected to this container, I want comboboxes on some of the column fields. I'm using a TableFieldFactory for that, and it is working like a charm.
I want the combobox in each field to contain the distinct elements from its respective property. My solution to this was to implement a method in my Container class that iterate through all properties in the container and for each property creates a new IndexedContainer with unique values from that property. The method returns a map with PropertyIds/Containers, so that I, in createField(), can pick each container from each property I want to have comboboxes for.
Example
So, say I have three propertyId's, Foo, Bar and Baz which each "contains" several items of which some are the same, like so:
Foo
Chris
Chris
Meg
Meg
Meg
Stewie
Stewie
... and the same for Bar and Baz, only other values...
The getDistinctContainers() method returns a Map, looking like this:
Key: PropertyId: Foo
Value: Container: contains propertyId [Foo] and the unique values of Foo, ie. [Chris, Meg, Stewie]
Key: PropertyId: Bar
Value: ... and so forth...
When I am about to set the container datasource in createField(), the container looks like this (for property Foo):
allItemIds: [0, 1, 2]
items: {2={Foo=Stewie}, 1={Foo=Meg}, 0={Foo=Chris}}
propertyIds: [Foo]
... which seems alright to me...
Now, the table shows the comboboxes in each field as intended. But when I click an item in a combobox, it gives me the following conversion error:
com.vaadin.data.util.converter.Converter$ConversionException: Unable to convert value of type java.lang.Integer to model type class java.lang.String. No converter is set and the types are not compatible.
at com.vaadin.data.util.converter.ConverterUtil.convertToModel(ConverterUtil.java:181)
at com.vaadin.ui.AbstractField.convertToModel(AbstractField.java:745)
Note:
I tried creating the same scenario outside of the table, and it worked just fine. So it seems that the comboboxes, with the same logic and the same containers, works fine outside the TableFieldFactory and the createFields() method. I can't put my finger on why they shouldn't work in a TableFieldFactory...
Question:
What do I do to get the comboboxes to set the correct values?
Here's my Container class:
public class SomeContainer extends IndexedContainer {
private static final long serialVersionUID = 1L;
public void addContainerProperties() {
addContainerProperty("Foo", String.class, null);
addContainerProperty("Bar", String.class, null);
addContainerProperty("Baz", String.class, null);
}
public Map<String, Container> getDistinctContainers() {
Map<String, Container> m = new HashMap<String, Container>();
ArrayList<Object> filter = new ArrayList<Object>();
int id = 0;
for (Object propertyId : this.getContainerPropertyIds()) {
Container cont = new IndexedContainer();
cont.addContainerProperty(propertyId, propertyId.getClass(), null);
for (Object itemId : this.getItemIds()) {
Object ob = ((Item) this.getItem(itemId)).getItemProperty(propertyId).getValue();
if ((!filter.contains(ob.toString())) && (ob != null)) {
filter.add(ob.toString());
Item item = cont.addItem(id);
item.getItemProperty(propertyId).setValue(ob);
id++;
}
}
m.put(propertyId.toString(), cont);
}
return m;
}
}
... and here is the relevant code for createField:
#Override
public Field<?> createField(Container container, Object itemId, Object propertyId, com.vaadin.ui.Component uiContext) {
TextField tField = (TextField) DefaultFieldFactory.get().createField(container, itemId, propertyId, uiContext);
tField.setImmediate(true);
// ...some code here that uses the TextField
if (propertyId.equals("Foo")) {
ComboBox select = new ComboBox();
for (Map.Entry<String, Container> entry : distinctContainers.entrySet()) {
if (entry.getKey().equals(propertyId)) {
select.setContainerDataSource(entry.getValue());
}
}
select.setImmediate(true);
select.setItemCaptionPropertyId(propertyId);
select.setItemCaptionMode(AbstractSelect.ITEM_CAPTION_MODE_PROPERTY);
return select;
}
// ... if statements for Bar and Baz left out for brevity...
return tField;
}
Please help me understand what I'm missing!
Thanks in advance!
From the above exception and code snippets we can see that a conversion between Integer (presentation type) and String (model) is required. In this particular case:
presentation type: ItemId = {0,1,2}
model: value of PropertyId = {"Chris", "Meg", "Stewie"}
Since Vaadin has no no built-in IntegerToStringConverter you would need a custom converter:
...
select.setContainerDataSource(entry.getValue());
select.setConverter(new Converter<Object, String>() {
#Override
public String convertToModel(Object itemId, Class<? extends String> paramClass, Locale paramLocale) throws ConversionException {
if (itemId != null) {
IndexedContainer c = (IndexedContainer) entry.getValue();
Object propertyId = c.getContainerPropertyIds().iterator().next();
Object name = c.getItem(itemId).getItemProperty(propertyId).getValue();
return (String) name;
}
return null;
}
#Override
public Object convertToPresentation(String value, Class<? extends Object> paramClass, Locale paramLocale) throws ConversionException {
if (value != null) {
IndexedContainer c = (IndexedContainer) entry.getValue();
Object propertyId = c.getContainerPropertyIds().iterator().next();
for (Object itemId : container.getItemIds()) {
Object name = c.getContainerProperty(itemId, propertyId).getValue();
if (value.equals(name)) {
return itemId;
}
}
}
return null;
}
#Override
public Class<String> getModelType() {
return String.class;
}
#Override
public Class<Object> getPresentationType() {
return Object.class;
}
});
Please notice that is not possible to use explicit Integer<-->String conversion
select.setConverter(new Converter<Integer, String>());
as compiler rejects it. The problem has been described here.
More about Vaadin's converters can be found at:
Book of Vaadin, Chapter 9.2.3: Converting Between Property Type and Representation
Changing the default converters for an application
Creating your own converter for String - MyType conversion
I hope it helps.