Bindings für Vaadin Flow UI Benutzeroberflächen

Seife für Vaadin Paket
Formulare und Listen aus JPA Datenmodellen erstellen

Direkt aus dem JPA Datenmodell Eingabemasken (Forms) und Listen (Grids) erstellen die die passende Anbindung an das Datenmodell enthalten. Mit “Seife” können Sie mit nur wenigen Direktiven sauberen Programmcode für das Databinding erstellen.

Während des Kompilierens werden die Annotationen am Datenmodell und an den UI Klassen ausgewertet. Ein Binding für die entsprechenden Felder des Modells wird basierend auf dem Datentyp von Code-Vorlagen erstellt. Die deklarative Definition erhält die Lesbarkeit des Codes. Zugleich bleiben insbesondere große Code-Bases aufgrund der Templates auch zukünftig wartbar.
Im Datenmodell @SeifeClass an der Klasse hinzufügen und die benötigten Felder mit @SeifeField markieren. Per Konvention haben die UI elemente den gleichen Namen wie die Attribute der Entity. Falls das nicht möglich ist, kann das referenzierte Attribut explizit definiert werden. Auch eine Punkt-Notation wird unterstützt um verschachtelte Daten anzubinden.

@Entity
@SeifeClass // just add this for the form binding, no special options needed
public class Customer {
    @Id
    @GeneratedValue(strategy=GenerationType.AUTO)
    private Long id;
    /**
     * This field is exposed to the forms via @SeifeField
     */
    @SeifeField
    private String firstName;
    /**
     * The mandatory flag will create a mandatory input field whenever it is bound.
     */
    @SeifeField(mandatory = true)
    private String lastName;
    /**
     * Embedded address, this can also be a regular database reference
     */
    @SeifeField
    @Embedded
    private Address address;
// [..]
}

Genauso einfach bekommt die Klasse der Benutzeroberfläche die @SeifeForm(forClass=Model.class) Annotation um sie als UI für das Formular oder Grid festzulegen. Standardmäßig werden nur noch alle Eingabefelder mit @SeifeBinding markiert. In diesem Beispiel ist eine List für die Klasse Customer definiert.

@SpringComponent
@Scope(SCOPE_PROTOTYPE)
@SeifeForm(forClass = Customer.class, generatorOptions = {"grid.gridClass"})
public class CustomerGrid extends Grid<Customer> {

    /**
     * The recommended way is to name the fields just as in e.g. {@link Customer}, fields are then bound implicitly.
     */
    @SeifeBinding
    private Column<Customer> firstName;

    /**
     * However if necessary it can be explicitly bound via the name.
     */
    @SeifeBinding("lastName")
    private Column<Customer> name;

    /**
     * You can navigate fields with the dot-notation, even if they may contain null and are not mandatory.
     */
    @SeifeBinding("address.street")
    private Column<Customer> street;
// [..]
}

Keine weitere Abstraktionsschicht und Library-Dependency wird benötigt. Die lizensierte Version ermöglicht es eigene Template-Vorlagen zu erstellen und bestehende an die individuellen Anforderungen anzupassen.
Der Vorteil bei dieser Vorgehensweise ist ein sauberer und konsistenter Code im gesamten Projekt. Wenn ein Datentyp sich in der Zukunft verändern sollte wird das Template angepasst und somit die Änderung an allen Stellen der Benutzeroberfläche in gleicher Weise durchgeführt.

Hier ein Auszug aus dem generierten Programm für die Listview;

  protected final void setupColumns() {
    firstName = addColumn(entity -> entity.getFirstName());
    firstName.setKey(COLUMN_FIRST_NAME);
    firstName.setId("firstName");
    name = addColumn(entity -> entity.getLastName());
    name.setKey(COLUMN_LAST_NAME);
    name.setId("name");
    street = addColumn(entity -> Optional.ofNullable(entity.getAddress()).map(p -> p.getStreet()).orElse(null));
    street.setKey(COLUMN_ADDRESS_STREET);
    street.setId("street");
    birthday = addColumn(entity -> entity.getBirthday());
    birthday.setKey(COLUMN_BIRTHDAY);
    birthday.setId("birthday");
    password = addColumn(entity -> entity.getPassword());
    password.setKey(COLUMN_PASSWORD);
    password.setId("password");
    favoriteProductName = addColumn(entity -> Optional.ofNullable(entity.getFavoriteProduct()).map(p -> p.getName()).orElse(null));
    favoriteProductName.setKey(COLUMN_FAVORITE_PRODUCT_NAME);
    favoriteProductName.setId("favoriteProductName");
    premium = addComponentColumn(entity -> {
        Checkbox cb = new Checkbox(entity.isPremium());
        cb.setEnabled(false);
        return cb;
      });
   }


So können die Bindings deklarativ festgelegt werden ohne die Ausdrucksstärke von ‘normalem’ Code einzubüßen der verloren geht, wenn generischen Klassen verwendet werden und mit Reflection gearbeitet werden würde. Selbst Stacktraces sind kürzer und einfacher zu verstehen.

Das System kann automatisch z.B. CheckBoxen einfügen wann immer ein boolean oder java.lang.Boolean Datentyp im Modell benutzt wird. Die Anbindung an Custom-Types wird in gleicher Weise unterstützt.

Ein vollständiges Beispiel findet sich im Download der Test- oder Vollversion.

Weitere Informationen: Documentation (engl.)