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

Java + Swing
dependencies {
    compile 'org.codehaus.griffon.plugins:griffon-glazedlists-swing:1.3.1'
}
Groovy + Swing
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

Java + Swing
<dependency>
    <groupId>org.codehaus.griffon.plugins</groupId>
    <artifactId>griffon-glazedlists-swing</artifactId>
</dependency>
Groovy + Swing
<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

Table 1. defaultTableFormat
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

Table 2. defaultAdvancedTableFormat
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

Table 3. defaultWritableTableFormat
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();
Table 4. eventComboBoxModel
Property Type Required Bindable Notes

source

EventList

yes

no

wrap

boolean

no

no

wrap source with Thread safe proxy

Table 5. eventListModel
Property Type Required Bindable Notes

source

EventList

yes

no

wrap

boolean

no

no

wrap source with Thread safe proxy

Table 6. eventTableModel
Property Type Required Bindable Notes

source

EventList

yes

no

format

TableFormat

yes

no

wrap

boolean

no

no

wrap source with Thread safe proxy

Table 7. eventTreeModel
Property Type Required Bindable Notes

source

TreeList

yes

no

Table 8. eventJXTableModel
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 current node

source

EventList

strategy

Object

AbstractTableComparatorChooser.SINGLE_COLUMN

installTTreeTableSupport(Map args) - install a TableComparatorChooser on a target JTable

Argument Type Default

target

JTable

builder’s current node

source

TreeList

index

int

1

installComboBoxAutoCompleteSupport(Map args) - install a TableComparatorChooser on a target JTable

Argument Type Default

target

JComboBox

builder’s current node

items

EventList

textFilterator

TextFilterator

format

Format

installEventSelectionModel(Map args) - install an EventSelectionModel on a target JTable

Argument Type Default

target

JComboBox

builder’s current node

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 current node

source

SortedList

multiple

boolean

false

3.3. JavaFX

Module name: glazedlists-javafx-groovy

Depends on: javafx-groovy

bind(BuilderCustomizer.class)
    .to(GlazedlistsJavaFXBuilderCustomizer.class)
    .asSingleton();
Table 9. fxTableFormat
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

Table 10. fxWritableTableFormat
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?

Table 11. eventTableViewModel
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.