M
- the type of the model class.@Beta public class ModelWrapper<M> extends Object
A typical workflow would be:
Additional requirements:
These requirements are quite common but there is a lot of code needed to copy between the model and the viewModel. Additionally we have a tight coupling because every time the structure of the model changes (for example a field is removed) we have several places in the viewModel that need to be adjusted.
This component can be used to simplify use cases like the described one and minimize the coupling between the model
and the viewModel. See the following code example. First without and afterwards with the ModelWrapper
.
The model class:
public class Person { private String name; private String familyName; private int age; public String getName() { return name; } public void setName(String name) { this.name = name; } public String getFamilyName() { return familyName; } public void setFamilyName(String familyName) { this.familyName = familyName; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } }Without
ModelWrapper
:
public class PersonViewModel implements ViewModel { private StringProperty name = new SimpleStringProperty(); private StringProperty familyName = new SimpleStringProperty(); private IntegerProperty age = new SimpleIntegerProperty(); private Person person; public void init(Person person) { this.person = person; reloadFromModel(); } public void reset() { this.name.setValue(""); this.familyName.setValue(""); this.age.setValue(0); } public void reloadFromModel() { this.name.setValue(person.getName()); this.familyName.setValue(person.getFamilyName()); this.age.setValue(person.getAge()); } public void save() { if (someValidation() && person != null) { person.setName(name.getValue()); person.setFamilyName(familyName.getValue()); person.setAge(age.getValue()); } } public StringProperty nameProperty() { return name; } public StringProperty familyNameProperty() { return familyName; } public IntegerProperty ageProperty() { return age; } }With
ModelWrapper
:
public class PersonViewModel implements ViewModel { private ModelWrapper wrapper = new ModelWrapper(); public void init(Person person) { wrapper.set(person); wrapper.reload(); } public void reset() { wrapper.reset(); } public void reloadFromModel(){ wrapper.reload(); } public void save() { if (someValidation()) { wrapper.commit(); } } public Property nameProperty(){ return wrapper.field("name", Person::getName, Person::setName, ""); } public Property familyNameProperty(){ return wrapper.field("familyName", Person::getFamilyName, Person::setFamilyName, ""); } public Property ageProperty() { return wrapper.field("age", Person::getAge, Person::setAge, 0); } }In the first example without the
ModelWrapper
we have several lines of code that are specific for each field
of the model. If we would add a new field to the model (for example "email") then we would have to update several
pieces of code in the ViewModel.
On the other hand in the example with the ModelWrapper
there is only the definition of the Property accessors
in the bottom of the class that is specific to the fields of the Model. For each field we have only one place in the
ViewModel that would need an update when the structure of the model changes.
Constructor and Description |
---|
ModelWrapper()
Create a new instance of
ModelWrapper that is empty at the moment. |
ModelWrapper(M model)
Create a new instance of
ModelWrapper that wraps the given instance of the Model class. |
Modifier and Type | Method and Description |
---|---|
void |
commit()
Take the current value of each property field and write it into the wrapped model element.
|
<T> javafx.beans.property.Property<T> |
field(java.util.function.Function<M,T> getter,
java.util.function.BiConsumer<M,T> setter)
Add a new field to this instance of the wrapper.
|
<T> javafx.beans.property.Property<T> |
field(java.util.function.Function<M,T> getter,
java.util.function.BiConsumer<M,T> setter,
T defaultValue)
Add a new field to this instance of the wrapper.
|
<T> javafx.beans.property.Property<T> |
field(java.util.function.Function<M,javafx.beans.value.WritableValue<T>> accessor)
Add a new field to this instance of the wrapper.
|
<T> javafx.beans.property.Property<T> |
field(java.util.function.Function<M,javafx.beans.value.WritableValue<T>> accessor,
T defaultValue)
Add a new field to this instance of the wrapper.
|
<T> javafx.beans.property.Property<T> |
field(String fieldName,
java.util.function.Function<M,T> getter,
java.util.function.BiConsumer<M,T> setter)
Add a new field to this instance of the wrapper that is identified by the given string.
|
<T> javafx.beans.property.Property<T> |
field(String fieldName,
java.util.function.Function<M,T> getter,
java.util.function.BiConsumer<M,T> setter,
T defaultValue)
|
<T> javafx.beans.property.Property<T> |
field(String fieldName,
java.util.function.Function<M,javafx.beans.value.WritableValue<T>> accessor)
Add a new field to this instance of the wrapper that is identified by the given string.
|
<T> javafx.beans.property.Property<T> |
field(String fieldName,
java.util.function.Function<M,javafx.beans.value.WritableValue<T>> accessor,
T defaultValue)
|
M |
get() |
void |
reload()
Take the current values from the wrapped model element and put them in the corresponding property fields.
|
void |
reset()
Resets all defined fields to their default values.
|
void |
set(M model)
Define the model element that will be wrapped by this
ModelWrapper instance. |
public ModelWrapper(M model)
ModelWrapper
that wraps the given instance of the Model class.model
- the element of the model that will be wrapped.public ModelWrapper()
ModelWrapper
that is empty at the moment. You have to define the model element
that should be wrapped afterwards with the set(Object)
method.public void set(M model)
ModelWrapper
instance.model
- the element of the model that will be wrapped.public M get()
null
.public void reset()
null
will be used
instead.
Note: This method has no effects on the wrapped model element but will only change the values of the defined property fields.
public void commit()
If no model element is defined then nothing will happen.
Note: This method has no effects on the values of the defined property fields but will only change the state of the wrapped model element.
public void reload()
If no model element is defined then nothing will happen.
Note: This method has no effects on the wrapped model element but will only change the values of the defined property fields.
public <T> javafx.beans.property.Property<T> field(java.util.function.Function<M,javafx.beans.value.WritableValue<T>> accessor)
Example:
ModelWrapper personWrapper = new ModelWrapper(); Property wrappedNameProperty = personWrapper.field(person -> person.nameProperty()); // or with a method reference Property wrappedNameProperty = personWrapper.field(Person::nameProperty);
T
- the type of the field.accessor
- a function that returns the property for a given model instance. Typically you will use a method
reference to the javafx-property accessor method.public <T> javafx.beans.property.Property<T> field(java.util.function.Function<M,javafx.beans.value.WritableValue<T>> accessor, T defaultValue)
Additionally you can define a default value that is used as when reset()
is invoked.
Example:
ModelWrapper personWrapper = new ModelWrapper(); Property wrappedNameProperty = personWrapper.field(person -> person.nameProperty(), "empty"); // or with a method reference Property wrappedNameProperty = personWrapper.field(Person::nameProperty, "empty");
T
- the type of the field.accessor
- a function that returns the property for a given model instance. Typically you will use a method
reference to the javafx-property accessor method.defaultValue
- the default value for the field.public <T> javafx.beans.property.Property<T> field(java.util.function.Function<M,T> getter, java.util.function.BiConsumer<M,T> setter)
Example:
ModelWrapper personWrapper = new ModelWrapper(); Property wrappedNameProperty = personWrapper.field(person -> person.getName(), (person, value) -> person.setName(value), "empty"); // or with a method reference Property wrappedNameProperty = personWrapper.field(Person::getName, Person::setName, "empty");
T
- the type of the field.getter
- a function that returns the current value of the field for a given model element. Typically you will
use a method reference to the getter method of the model element.setter
- a function that sets the given value to the given model element. Typically you will use a method
reference to the setter method of the model element.public <T> javafx.beans.property.Property<T> field(java.util.function.Function<M,T> getter, java.util.function.BiConsumer<M,T> setter, T defaultValue)
Additionally you can define a default value that is used as when reset()
is invoked.
Example:
ModelWrapper personWrapper = new ModelWrapper(); Property wrappedNameProperty = personWrapper.field(person -> person.getName(), (person, value) -> person.setName(value), "empty"); // or with a method reference Property wrappedNameProperty = personWrapper.field(Person::getName, Person::setName, "empty");
T
- the type of the field.getter
- a function that returns the current value of the field for a given model element. Typically you will
use a method reference to the getter method of the model element.setter
- a function that sets the given value to the given model element. Typically you will use a method
reference to the setter method of the model element.defaultValue
- the default value for the field.public <T> javafx.beans.property.Property<T> field(String fieldName, java.util.function.Function<M,javafx.beans.value.WritableValue<T>> accessor)
field(Function)
with one difference: This method can be invoked multiply times but will only
create a single field instance for every given string identifier. This means that the returned property will be
the same instance for each call with the same identifier.
This behaviour can be useful when you don't keep a reference to the returned property but directly call this method in a property accessor method in your viewModel. This way only a single field wrapping will be defined even when the accessor method is called multiple times. See the following example of a typical use case:
public class PersonViewModel extends ViewModel { // you only need a reference to the model wrapper itself but no additional references to each wrapped property. private ModelWrapper wrapper = new ModelWrapper(); // This method will be used from the view. // The view can call this method multiple times but will always get the same property instance. public Property nameProperty() { return wrapper.field("name", Person::nameProperty); } }
T
- the type of the field.fieldName
- the identifier for this field. Typically you will use the name of the field in the model.accessor
- a function that returns the property for a given model instance. Typically you will use a method
reference to the javafx-property accessor method.public <T> javafx.beans.property.Property<T> field(String fieldName, java.util.function.Function<M,javafx.beans.value.WritableValue<T>> accessor, T defaultValue)
field(String, Function)
. The difference is that this method accepts an additional parameter to
define the default value that will be used when reset()
is invoked.T
- the type of the field.fieldName
- the identifier for this field. Typically you will use the name of the field in the model.accessor
- a function that returns the property for a given model instance. Typically you will use a method
reference to the javafx-property accessor method.public <T> javafx.beans.property.Property<T> field(String fieldName, java.util.function.Function<M,T> getter, java.util.function.BiConsumer<M,T> setter)
field(Function, BiConsumer)
with one difference: This method can be invoked multiply times
but will only create a single field instance for every given string identifier. This means that the returned
property will be the same instance for each call with the same identifier.
This behaviour can be useful when you don't keep a reference to the returned property but directly call this method in a property accessor method in your viewModel. This way only a single field wrapping will be defined even when the accessor method is called multiple times. See the following example of a typical use case:
public class PersonViewModel extends ViewModel { // you only need a reference to the model wrapper itself but no additional references to each wrapped property. private ModelWrapper wrapper = new ModelWrapper(); // This method will be used from the view. // The view can call this method multiple times but will always get the same property instance. public Property nameProperty() { return wrapper.field("name", Person::getName, Person::setName); } }
T
- the type of the field.fieldName
- the identifier for this field. Typically you will use the name of the field in the model.getter
- a function that returns the current value of the field for a given model element. Typically you will
use a method reference to the getter method of the model element.setter
- a function that sets the given value to the given model element. Typically you will use a method
reference to the setter method of the model element.public <T> javafx.beans.property.Property<T> field(String fieldName, java.util.function.Function<M,T> getter, java.util.function.BiConsumer<M,T> setter, T defaultValue)
field(String, Function, BiConsumer)
. The difference is that this method accepts an additional
parameter to define the default value that will be used when reset()
is invoked.T
- the type of the field.fieldName
- the identifier for this field. Typically you will use the name of the field in the model.getter
- a function that returns the current value of the field for a given model element. Typically you will
use a method reference to the getter method of the model element.setter
- a function that sets the given value to the given model element. Typically you will use a method
reference to the setter method of the model element.Copyright © 2015 Saxonia Systems AG. All rights reserved.