JavaFX TableView with Database records
JavaFX has a nice looking TableView and it offers useful functionality, but the standard implementation is not useful for real-world database applications.
Why?
- You must fill a TableView manually with records from the database, because JavaFX has no datasource support integrated. This means you have to use JDBC directly or libraries like EclipseLink or Hibernate, to fill your table.
- If you fill your TableView manually, you miss support for database sort operations. JavaFX supports memory sort but if you have millions of records, it is not the best idea to fetch all records for simple sort operations.
- Support for lazy-loading/load-on-demand is not integrated. With load-on-demand, I mean the possibility to load records when the UI requests them. If you have a database table with e.g. 10 million records, the UI should load the first 1.000 (or more). If the user needs more records, the TableView should load more automatically. I won't use paging for this use case.
- Column resizing works well but is not user-friendly. The CONSTRAINED_RESIZE_POLICY is like auto-resize in JTable. It allows column resizing within the table width, but it is not possible to enlarge the table horizontally. The UNCONSTRAINED_RESIZE_POLICY allows enlarging but adds an extra "column". This column is not selectable and fills the gap if the total column width is smaller than the table width. We need a resize policy that supports both and such a policy is not included in JavaFX.
- The default selection model offers ROW and CELL selection, but in ROW mode you don't see the selected cell (= focus). If you need an editable TableView it is a problem if you can't see the selected cell.
- If you need an editable table, you have support but not a working solution - especially if you work with database records.
- Missing support for Insert and Delete rows with keyboard. It is possible to start updating values with <F2>.
- No standardized solution for Master/Detail with multiple levels.
I don't say that it is not possible to implement above features, but if you don't create a custom control, you'll have a lot of boilerplate code in your applications. Some features are not trivial because the APIs do not offer relevant methods and the JavaFX control development model hides a lot of functionality. Of course, it is always possible to create new controls, but it is not cool to reinvent the wheel. One problem is that the TableView implementation together with the TableViewSkin and TableViewBehavior are not very developer friendly, because the classes are not really extendable. Most important methods are private or final. Another problem is that you need css and code together, for specific features. It is easy to change padding with css but where is a method in the API?
A solution for the datasource problem is offered from the DataFX project.
I'm a developer for business application frameworks and most applications are connected to databases with or without an application server. The applications must be fast and responsive. It helps if an application looks fancy, but functionality is more important.
Sometimes I think that JavaFX controls are really impressive but their developers should create real-world (business) applications.
If you get feedback from your paying customers you know exactly what you have to do and which features are good and important. My personal opinion is that Oracle should ask application developers and work with them to solve real-world problems.
With JVx, we have a full-stack-application framework that solves our business problems and we try to implement new UI technologies as soon as they are market relevant. So we started with the integration of JavaFX. We tried to solve above points, because they are very important for us.
Our current development progress is available as OpenSource (sub)Project of JVx and is licensed under Apache 2.0.
What we have solved so far?
All above points, but more or less clean. Some solutions are "dirty" because we have no API or didn't find a better solution.
"Dirty" solutions?
- Column resize policies are supported but relevant features are missing or hidden, e.g. The TableView retrieves the content-width from the TableViewSkin (via general control properties - getProperties()) because the TableView has no access to scrollbars and the content width is different with and without vertical scrollbar. This is dirty and does not work in any cases in the original TableView, but this information is important if you develop your own resize policy! We need more and better access through the API.
- Load-on-demand works but this is the dirtiest hack. It is not so easy to add a feature that was not planned. We used a custom event dispatcher to made it possible. We must check the implementation with every TableView update...
- Show focused cell in ROW selection mode is possible with CSS but it is horrible.
There are more funny solutions, but the result just works. One additional and maybe interesting feature is, that we don't change the row height if you start editing. It was annoying that the table height toggled in edit mode.
Our current solution offers an editable TableView which is connected to a database (2 or 3-tier architecture). All sort operations are delegated to the database. The solution supports any Master/Detail relations (memory, db and mixed) and loads records automatically from the database. All Insert/Update/Delete operations are sent to the database.
Our current solution is not finished because we don't have specific cell editors. We have to implement cell editors for Number/Date/Images/LOVs (= ComboBox).
A first impression:
Country - States - Districts
with Master/Detail/Detail |
The database:
(
ID INTEGER IDENTITY,
COUNTRY VARCHAR(200) NOT NULL,
EU CHAR(1) DEFAULT 'N' NOT NULL,
constraint CTRY_UK UNIQUE (COUNTRY)
)
CREATE TABLE STATES
(
ID INTEGER IDENTITY,
CTRY_ID INTEGER NOT NULL,
STATE VARCHAR(200) NOT NULL,
constraint STAT_UK UNIQUE (STATE),
constraint STAT_CTRY_ID_FK FOREIGN KEY (CTRY_ID) REFERENCES COUNTRIES (ID)
)
CREATE TABLE DISTRICTS
(
ID INTEGER IDENTITY,
STAT_ID INTEGER NOT NULL,
DISTRICT VARCHAR(200),
constraint DIST_UK UNIQUE (DISTRICT),
constraint DIST_STAT_ID_FK FOREIGN KEY (STAT_ID) REFERENCES STATES (ID)
)