1. Introduction
GlazedLists - List transformations in Java.
Griffon version: 2.5.0
1.1. JavaFX Support
1.1.1. EventList as ObservableList
An EventList
can be used as source where an ObservableList
is expected as long as it’s wrapped
with a EventObservableList
.
Remember to call dispose() on the EventObservableList when it’s no longer needed.
|
This bridge class allows you to build a full GlazedLists pipeline, adapting it to be consumed by widgets such as
ListView
and TableView
.
1.1.2. TableViewModel
JavaFX widgets do not follow the same MVC approach as Swing components; this means advanced widgets such as TableView
do not offer a model
class, rather they consume an ObservableList
directly, relying on helper classes such as
TableColumn
to customize the view and behavior of cells.
GlazedLists has excellent support for transforming an EventList
into a Swing TableModel
and we believe this feature
should be made available to JavaFX too. This is why this plugin delivers the following model classes:
The former model works with implementations of the standard TableFormat
while the latter works with the more
JavaFX friendly FXTableFormat
. The difference between formats is that the standard works with any kind of POJO
while the other one is aware of JavaFX observable values.
The following example shows a simple GlazedLists pipeline. The Person
class is supposed to be a non-observable domain
object. The ObservablePerson
class wraps the domain as an observable. The pipeline is assembled in such a way that
changes made to the observable beans inside an editable TableView
are propagated and immediately visible in the ListView
that consumes the same ObservableList
.
package griffon.plugins.glazedlists.javafx;
import ca.odell.glazedlists.EventList;
import ca.odell.glazedlists.GlazedLists;
import ca.odell.glazedlists.ObservableElementList;
import griffon.plugins.glazedlists.javafx.gui.DefaultFXWritableTableFormat;
import griffon.plugins.glazedlists.javafx.gui.FXTableFormat;
import griffon.plugins.glazedlists.javafx.models.FXTableViewModel;
import javafx.application.Application;
import javafx.beans.property.Property;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.scene.Scene;
import javafx.scene.control.ListView;
import javafx.scene.control.TableView;
import javafx.scene.layout.GridPane;
import javafx.stage.Stage;
import javax.annotation.Nonnull;
import java.util.stream.Collectors;
import static griffon.plugins.glazedlists.javafx.GlazedListsJavaFX.eventTableViewModel;
import static griffon.plugins.glazedlists.javafx.GlazedListsJavaFX.propertyContainerConnector;
import static griffon.plugins.glazedlists.javafx.gui.FXTableFormat.option;
import static griffon.plugins.glazedlists.javafx.gui.FXTableFormat.options;
public class GlazedListsFXSample extends Application {
public static void main(String[] args) throws Exception {
launch(args);
}
@Override
public void start(Stage stage) throws Exception {
// non-observable beans
ObservableList<Person> people = FXCollections.observableArrayList(
new Person("Jamie", "Hyneman"),
new Person("Adam", "Savage"),
new Person("Tory", "Belleci"),
new Person("Kari", "Byron"),
new Person("Grant", "Imahara")
);
// observable beans
EventList<ObservablePerson> evenList = GlazedLists.eventList(
people.stream().map(ObservablePerson::new).collect(Collectors.<ObservablePerson>toList())
);
// a list that reacts to element updates
EventList<ObservablePerson> op = new ObservableElementList<>(evenList, propertyContainerConnector());
// bridge between EventList and ObservableList
ObservableList<ObservablePerson> observablePeople = new EventObservableList<>(op);
// define format options
FXTableFormat<ObservablePerson> tableFormat = new DefaultFXWritableTableFormat<>(
options(option("name", "name"), option("editable", true)),
options(option("name", "lastname"), option("editable", true))
);
// table model backed by an ObservableList & TableFormat
FXTableViewModel<ObservablePerson> tableModel = eventTableViewModel(observablePeople, tableFormat);
// create the table
TableView<ObservablePerson> tableView = new TableView<>();
// attach the model to the table
tableModel.attachTo(tableView);
tableView.setEditable(true);
// watch edits on tbale being pushed to list
ListView<ObservablePerson> listView = new ListView<>();
listView.setItems(observablePeople);
// put everything in a Grid
GridPane grid = new GridPane();
grid.add(tableView, 0, 0);
grid.add(listView, 1, 0);
// show it
Scene scene = new Scene(grid);
stage.setTitle("MythBusters");
stage.setScene(scene);
stage.sizeToScene();
stage.show();
}
// non-observable bean
public static class Person {
private final String name;
private final String lastname;
public Person(String name, String lastname) {
this.name = name;
this.lastname = lastname;
}
public String getName() {
return name;
}
public String getLastname() {
return lastname;
}
}
// observable bean
public static class ObservablePerson implements PropertyContainer {
private StringProperty name;
private StringProperty lastname;
public ObservablePerson(Person person) {
this(person.getName(), person.getLastname());
}
public ObservablePerson(String name, String lastname) {
setName(name);
setLastname(lastname);
}
@Override
@Nonnull
public Property<?>[] properties() {
return new Property<?>[]{
nameProperty(), lastnameProperty()
};
}
public StringProperty nameProperty() {
if (name == null) {
name = new SimpleStringProperty(this, "name", "");
}
return name;
}
public StringProperty lastnameProperty() {
if (lastname == null) {
lastname = new SimpleStringProperty(this, "lastname", "");
}
return lastname;
}
public String getName() {
return nameProperty().get();
}
public String getLastname() {
return lastnameProperty().get();
}
public void setName(String name) {
nameProperty().set(name);
}
public void setLastname(String lastname) {
lastnameProperty().set(lastname);
}
@Override
public String toString() {
return name.get() + " " + lastname.get();
}
}
}
2. Configuration
The plugin delivers artifacts for both Swing and JavaFX. It also contains Groovy enhancements that can be used in combination with the respective UI toolkit DSL (SwingBuilder and GroovyFX).
2.1. Gradle
You have two options for configuring this plugin: automatic and manual.
2.1.1. Automatic
As long as the project has the org.codehaus.griffon.griffon
plugin applied to it you
may include the following snippet in build.gradle
dependencies {
griffon 'org.codehaus.griffon.plugins:griffon-glazedlists-plugin:1.3.1'
}
The griffon
plugin will take care of the rest given its configuration.
2.1.2. Manual
You will need to configure any of the following blocks depending on your setup
dependencies {
compile 'org.codehaus.griffon.plugins:griffon-glazedlists-swing:1.3.1'
}
dependencies {
compile 'org.codehaus.griffon.plugins:griffon-glazedlists-swing-groovy:1.3.1'
}
2.2. Maven
First configure the griffon-glazedlists-plugin
BOM in your POM file, by placing the following
snippet before the <build>
element
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.codehaus.griffon.plugins</groupId>
<artifactId>griffon-glazedlists-plugin</artifactId>
<version>1.3.1</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
Next configure dependencies as required by your particular setup
<dependency>
<groupId>org.codehaus.griffon.plugins</groupId>
<artifactId>griffon-glazedlists-swing</artifactId>
</dependency>
<dependency>
<groupId>org.codehaus.griffon.plugins</groupId>
<artifactId>griffon-glazedlists-swing-groovy</artifactId>
</dependency>
3. Modules
The following sections display all bindings per module. Use this information to successfully override a binding on your own modules or to troubleshoot a module binding if the wrong type has been applied by the Griffon runtime.
3.1. Core
Module name: glazedlists-core-groovy
Depends on: groovy
bind(BuilderCustomizer.class)
.to(GlazedlistsCoreBuilderCustomizer.class)
.asSingleton();
3.1.1. Nodes
The following nodes will become available on a Groovy View
Property | Type | Required | Bindable | Notes |
---|---|---|---|---|
columnNames |
List |
yes |
no |
|
columns |
List<Map<String, ?>> |
yes |
no |
|
columns.name |
String |
yes |
no |
column’s name |
columns.title |
String |
no |
no |
column’s title |
columns.read |
ColumnReader |
no |
no |
element property reader |
Property | Type | Required | Bindable | Notes |
---|---|---|---|---|
columns |
List<Map<String, ?>> |
yes |
no |
|
columns.name |
String |
yes |
no |
column’s name |
columns.title |
String |
no |
no |
column’s title |
columns.class |
Class |
no |
no |
column’s class |
columns.comparator |
Comparator |
no |
no |
column’s comparator |
columns.read |
ColumnReader |
no |
no |
element property reader |
Property | Type | Required | Bindable | Notes |
---|---|---|---|---|
columns |
List<Map<String, ?>> |
yes |
no |
|
columns.name |
String |
yes |
no |
column’s name |
columns.title |
String |
no |
no |
column’s title |
columns.class |
Class |
no |
no |
column’s class |
columns.comparator |
Comparator |
no |
no |
column’s comparator |
columns.read |
ColumnReader |
no |
no |
element property reader |
columns.write |
ColumnWriter |
no |
no |
element property writer |
columns.editable |
ColumnEdit |
no |
no |
is this column editable? |
3.1.2. MetaProgramming
The following classes have been enhanced using the Groovy Module Extension feature of Groovy 2.0
ca.odell.glazedlists.util.concurrent.Lock
-
withLock(Closure) - this method executes the closure in the context of Lock, by acquiring and releasing the lock around the execution; like this
lock.lock()
try { closure() }
finally { lock.unlock() }
ca.odell.glazedlists.EventList
-
withReadLock(Closure) - builds on top of
Lock.withLock
, decorating the List’s ReadLock. -
withWriteLock(Closure) - builds on top of
Lock.withLock
, decorating the List’s WriteLock.
3.2. Swing
Module name: glazedlists-swing-groovy
Depends on: swing-groovy
bind(BuilderCustomizer.class)
.to(GlazedlistsSwingBuilderCustomizer.class)
.asSingleton();
Property | Type | Required | Bindable | Notes |
---|---|---|---|---|
source |
EventList |
yes |
no |
|
wrap |
boolean |
no |
no |
wrap source with Thread safe proxy |
Property | Type | Required | Bindable | Notes |
---|---|---|---|---|
source |
EventList |
yes |
no |
|
wrap |
boolean |
no |
no |
wrap source with Thread safe proxy |
Property | Type | Required | Bindable | Notes |
---|---|---|---|---|
source |
EventList |
yes |
no |
|
format |
TableFormat |
yes |
no |
|
wrap |
boolean |
no |
no |
wrap source with Thread safe proxy |
Property | Type | Required | Bindable | Notes |
---|---|---|---|---|
source |
TreeList |
yes |
no |
Property | Type | Required | Bindable | Notes |
---|---|---|---|---|
source |
EventList |
yes |
no |
|
format |
TableFormat |
yes |
no |
The wrap:
property in eventComboBoxModel
, eventListModel
and eventTableModel
defaults to true
.
3.2.1. Methods
The following methods become available as well
installTableComparatorChooser(Map args) - install a TableComparatorChooser on a target JTable
Argument | Type | Default |
---|---|---|
target |
JTable |
builder’s |
source |
EventList |
|
strategy |
Object |
AbstractTableComparatorChooser.SINGLE_COLUMN |
installTTreeTableSupport(Map args) - install a TableComparatorChooser on a target JTable
Argument | Type | Default |
---|---|---|
target |
JTable |
builder’s |
source |
TreeList |
|
index |
int |
1 |
installComboBoxAutoCompleteSupport(Map args) - install a TableComparatorChooser on a target JTable
Argument | Type | Default |
---|---|---|
target |
JComboBox |
builder’s |
items |
EventList |
|
textFilterator |
TextFilterator |
|
format |
Format |
installEventSelectionModel(Map args) - install an EventSelectionModel on a target JTable
Argument | Type | Default |
---|---|---|
target |
JComboBox |
builder’s |
source |
EventList |
|
selectionMode |
int |
ListSelectionModel.SINGLE_SELECTION |
installJXTableSorting(Map args) - using a JXTables native sorting system instead of glazedlists
Argument | Type | Default |
---|---|---|
target |
JComboBox |
builder’s |
source |
SortedList |
|
multiple |
boolean |
false |
3.3. JavaFX
Module name: glazedlists-javafx-groovy
Depends on: javafx-groovy
bind(BuilderCustomizer.class)
.to(GlazedlistsJavaFXBuilderCustomizer.class)
.asSingleton();
Property | Type | Required | Bindable | Notes |
---|---|---|---|---|
columnNames |
List |
yes |
no |
|
columns |
List<Map<String, ?>> |
yes |
no |
|
columns.name |
String |
yes |
no |
column’s name |
columns.title |
String |
no |
no |
column’s title |
columns.read |
ColumnReader |
no |
no |
element property reader |
Property | Type | Required | Bindable | Notes |
---|---|---|---|---|
columns |
List<Map<String, ?>> |
yes |
no |
|
columns.name |
String |
yes |
no |
column’s name |
columns.title |
String |
no |
no |
column’s title |
columns.read |
ColumnReader |
no |
no |
element property reader |
columns.write |
ColumnWriter |
no |
no |
element property writer |
columns.editable |
ColumnEdit |
no |
no |
is this column editable? |
Property | Type | Required | Bindable | Notes |
---|---|---|---|---|
source |
EventList |
yes |
no |
|
format |
TableFormat |
yes |
no |
|
wrap |
boolean |
no |
no |
wrap source with Thread safe proxy |
The wrap:
property in eventTableViewModel
defaults to true
.