diff --git a/README.md b/README.md index cdb3cbe99..b6a9cf912 100644 --- a/README.md +++ b/README.md @@ -20,7 +20,7 @@ This is the stable release that can be used in production. de.saxsys mvvmfx - 1.5.1 + 1.6.0 ``` @@ -32,7 +32,7 @@ Here we make bug fixes for the current stable release. de.saxsys mvvmfx - 1.5.2-SNAPSHOT + 1.6.1-SNAPSHOT ``` @@ -44,7 +44,7 @@ Here we develop new features. This release is unstable and shouldn't be used in de.saxsys mvvmfx - 1.6.0-SNAPSHOT + 1.7.0-SNAPSHOT ``` diff --git a/deploy_release.sh b/deploy_release.sh index 5c283670f..a35aa83ac 100755 --- a/deploy_release.sh +++ b/deploy_release.sh @@ -25,4 +25,4 @@ # # -mvn clean deploy -pl 'mvvmfx,mvvmfx-cdi,mvvmfx-guice,mvvmfx-archetype,mvvmfx-utils,mvvmfx-testing-utils' -am -DskipTests=true -Pdeploy-release --settings ~/.m2/settings-mvvmfx.xml +mvn clean deploy -pl 'mvvmfx,mvvmfx-cdi,mvvmfx-guice,mvvmfx-easydi,mvvmfx-archetype,mvvmfx-utils,mvvmfx-testing-utils,mvvmfx-validation' -am -DskipTests=true -Pdeploy-release --settings ~/.m2/settings-mvvmfx.xml diff --git a/examples/books-example/pom.xml b/examples/books-example/pom.xml index e3072a853..d6c97a89e 100644 --- a/examples/books-example/pom.xml +++ b/examples/books-example/pom.xml @@ -5,7 +5,7 @@ de.saxsys.mvvmfx examples - 1.5.2 + 1.6.0 4.0.0 @@ -35,9 +35,8 @@ mvvmfx - eu.lestard - easy-di - 0.2.0 + de.saxsys + mvvmfx-easydi de.jensd diff --git a/examples/books-example/src/main/java/de/saxsys/mvvmfx/examples/books/App.java b/examples/books-example/src/main/java/de/saxsys/mvvmfx/examples/books/App.java index 6b506dec5..a21d89895 100644 --- a/examples/books-example/src/main/java/de/saxsys/mvvmfx/examples/books/App.java +++ b/examples/books-example/src/main/java/de/saxsys/mvvmfx/examples/books/App.java @@ -1,33 +1,27 @@ package de.saxsys.mvvmfx.examples.books; -import javafx.application.Application; -import javafx.scene.Scene; -import javafx.scene.paint.Color; -import javafx.stage.Stage; - import com.guigarage.flatterfx.FlatterFX; import com.guigarage.flatterfx.FlatterInputType; - import de.saxsys.mvvmfx.FluentViewLoader; -import de.saxsys.mvvmfx.MvvmFX; +import de.saxsys.mvvmfx.easydi.MvvmfxEasyDIApplication; import de.saxsys.mvvmfx.examples.books.backend.LibraryService; import de.saxsys.mvvmfx.examples.books.backend.LibraryServiceImpl; import de.saxsys.mvvmfx.examples.books.backend.LibraryServiceMockImpl; import eu.lestard.easydi.EasyDI; +import javafx.scene.Scene; +import javafx.scene.paint.Color; +import javafx.stage.Stage; -public class App extends Application { +public class App extends MvvmfxEasyDIApplication { private static final boolean ENABLE_MOCK_SERVICE = true; - private EasyDI context = new EasyDI(); - - public static void main(String[] args) { launch(args); } - + @Override - public void start(Stage primaryStage) throws Exception { + protected void initEasyDi(EasyDI context) throws Exception { if (ENABLE_MOCK_SERVICE) { context.bindInterface(LibraryService.class, LibraryServiceMockImpl.class); LibraryServiceMockImpl libraryServiceMock = context.getInstance(LibraryServiceMockImpl.class); @@ -35,10 +29,11 @@ public void start(Stage primaryStage) throws Exception { } else { context.bindInterface(LibraryService.class, LibraryServiceImpl.class); } - - - MvvmFX.setCustomDependencyInjector(type -> context.getInstance(type)); - + } + + @Override + public void startMvvmfx(Stage primaryStage) throws Exception { + primaryStage.setTitle("Library JavaFX"); primaryStage.setMinWidth(1200); primaryStage.setMaxWidth(1200); diff --git a/examples/contacts-example/pom.xml b/examples/contacts-example/pom.xml index df88eac33..9542d4362 100644 --- a/examples/contacts-example/pom.xml +++ b/examples/contacts-example/pom.xml @@ -6,7 +6,7 @@ de.saxsys.mvvmfx examples - 1.5.2 + 1.6.0 contacts-example @@ -62,7 +62,7 @@ org.controlsfx controlsfx - 8.40.9 + 8.40.12 @@ -121,6 +121,10 @@ mvvmfx-testing-utils test + + de.saxsys + mvvmfx-validation + \ No newline at end of file diff --git a/examples/contacts-example/src/main/java/de/saxsys/mvvmfx/examples/contacts/model/CountrySelector.java b/examples/contacts-example/src/main/java/de/saxsys/mvvmfx/examples/contacts/model/CountrySelector.java index 8e5a9ab5c..751522c79 100644 --- a/examples/contacts-example/src/main/java/de/saxsys/mvvmfx/examples/contacts/model/CountrySelector.java +++ b/examples/contacts-example/src/main/java/de/saxsys/mvvmfx/examples/contacts/model/CountrySelector.java @@ -1,12 +1,5 @@ package de.saxsys.mvvmfx.examples.contacts.model; -import java.io.File; -import java.io.IOException; -import java.net.URL; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; import javafx.beans.property.ReadOnlyBooleanProperty; import javafx.beans.property.ReadOnlyBooleanWrapper; import javafx.beans.property.ReadOnlyStringProperty; @@ -14,19 +7,22 @@ import javafx.collections.FXCollections; import javafx.collections.ObservableList; import javafx.concurrent.Worker; - -import javax.xml.bind.annotation.XmlAccessType; -import javax.xml.bind.annotation.XmlAccessorType; -import javax.xml.bind.annotation.XmlAttribute; -import javax.xml.bind.annotation.XmlElement; -import javax.xml.bind.annotation.XmlRootElement; - import org.datafx.provider.ListDataProvider; -import org.datafx.reader.FileSource; +import org.datafx.reader.DataReader; +import org.datafx.reader.InputStreamDataReader; +import org.datafx.reader.converter.InputStreamConverter; import org.datafx.reader.converter.XmlConverter; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import javax.xml.bind.annotation.*; +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + /** * This class is used to encapsulate the process of loading available countries * and there subdivisions (if available). @@ -98,7 +94,7 @@ public void setCountry(Country country) { * Load all countries from the XML file source with DataFX. */ void loadCountries() { - URL iso3166Resource = this.getClass().getResource(ISO_3166_LOCATION); + InputStream iso3166Resource = this.getClass().getResourceAsStream(ISO_3166_LOCATION); if (iso3166Resource == null) { throw new IllegalStateException("Can't find the list of countries! Expected location was:" + ISO_3166_LOCATION); @@ -107,7 +103,8 @@ void loadCountries() { XmlConverter countryConverter = new XmlConverter<>("iso_3166_entry", Country.class); try { - FileSource dataSource = new FileSource<>(new File(iso3166Resource.getFile()), countryConverter); + DataReader dataSource = new InputStreamSource<>( iso3166Resource, countryConverter ); + ListDataProvider listDataProvider = new ListDataProvider<>(dataSource); listDataProvider.setResultObservableList(countries); @@ -124,12 +121,19 @@ void loadCountries() { } } + static class InputStreamSource extends InputStreamDataReader { + public InputStreamSource(InputStream is, InputStreamConverter converter) throws IOException { + super(converter); + setInputStream(is); + } + } + /** * Load all subdivisions from the XML file source with DataFX. */ void loadSubdivisions() { - URL iso3166_2Resource = this.getClass().getResource(ISO_3166_2_LOCATION); + InputStream iso3166_2Resource = this.getClass().getResourceAsStream(ISO_3166_2_LOCATION); if (iso3166_2Resource == null) { throw new IllegalStateException("Can't find the list of subdivisions! Expected location was:" @@ -142,8 +146,10 @@ void loadSubdivisions() { ObservableList subdivisionsEntities = FXCollections.observableArrayList(); try { - FileSource dataSource = new FileSource<>(new File(iso3166_2Resource.getFile()), - converter); + + DataReader dataSource = + new InputStreamSource<>( iso3166_2Resource, converter ); + ListDataProvider listDataProvider = new ListDataProvider<>(dataSource); listDataProvider.setResultObservableList(subdivisionsEntities); diff --git a/examples/contacts-example/src/main/java/de/saxsys/mvvmfx/examples/contacts/ui/detail/DetailViewModel.java b/examples/contacts-example/src/main/java/de/saxsys/mvvmfx/examples/contacts/ui/detail/DetailViewModel.java index 29ec36df2..cdb82121e 100644 --- a/examples/contacts-example/src/main/java/de/saxsys/mvvmfx/examples/contacts/ui/detail/DetailViewModel.java +++ b/examples/contacts-example/src/main/java/de/saxsys/mvvmfx/examples/contacts/ui/detail/DetailViewModel.java @@ -4,6 +4,9 @@ import java.time.LocalDate; import java.time.format.DateTimeFormatter; +import java.util.Objects; +import java.util.Optional; +import java.util.StringJoiner; import javax.inject.Inject; @@ -26,6 +29,7 @@ import javafx.beans.property.ReadOnlyObjectProperty; import javafx.beans.property.ReadOnlyStringProperty; import javafx.beans.property.ReadOnlyStringWrapper; +import javafx.beans.value.ObservableValue; @ScopeProvider(scopes = ContactDialogScope.class) public class DetailViewModel implements ViewModel { @@ -101,19 +105,17 @@ protected void action() throws Exception { private void createBindingsForLabels(ReadOnlyObjectProperty contactProperty) { name.bind(emptyStringOnNull(map(contactProperty, contact -> { - StringBuilder result = new StringBuilder(); + StringJoiner joiner = new StringJoiner(" "); - String title = contact.getTitle(); - if (title != null && !title.trim().isEmpty()) { - result.append(title); - result.append(" "); - } + Optional.ofNullable(contact.getTitle()) + .filter(Objects::nonNull) + .filter(s -> !s.trim().isEmpty()) + .ifPresent(joiner::add); - result.append(contact.getFirstname()); - result.append(" "); - result.append(contact.getLastname()); + joiner.add(contact.getFirstname()); + joiner.add(contact.getLastname()); - return result.toString(); + return joiner.toString(); }))); email.bind(emptyStringOnNull(map(contactProperty, Contact::getEmailAddress))); @@ -184,12 +186,12 @@ private void createBindingsForLabels(ReadOnlyObjectProperty contactProp * string is used for the returned binding. Otherwise the value of the * source binding is used. */ - private StringBinding emptyStringOnNull(ObjectBinding source) { + private StringBinding emptyStringOnNull(ObservableValue source) { return Bindings.createStringBinding(() -> { - if (source.get() == null) { + if (source.getValue() == null) { return ""; } else { - return source.get(); + return source.getValue(); } }, source); } @@ -242,20 +244,6 @@ public ReadOnlyStringProperty countrySubdivisionLabelTextProperty() { return countrySubdivision.getReadOnlyProperty(); } - private String trimString(String string) { - if (string == null || string.trim().isEmpty()) { - return ""; - } - return string; - } - - private String trimStringWithPostfix(String string, String append) { - if (string == null || string.trim().isEmpty()) { - return ""; - } - return string + append; - } - private Contact getSelectedContactFromScope() { return getSelectedContactPropertyFromScope().get(); } diff --git a/examples/contacts-example/src/main/java/de/saxsys/mvvmfx/examples/contacts/ui/master/MasterTableViewModel.java b/examples/contacts-example/src/main/java/de/saxsys/mvvmfx/examples/contacts/ui/master/MasterTableViewModel.java index 9d0cc17c7..d55dc846f 100644 --- a/examples/contacts-example/src/main/java/de/saxsys/mvvmfx/examples/contacts/ui/master/MasterTableViewModel.java +++ b/examples/contacts-example/src/main/java/de/saxsys/mvvmfx/examples/contacts/ui/master/MasterTableViewModel.java @@ -13,85 +13,50 @@ public class MasterTableViewModel { - private final String id; - private final IntegerProperty age = new SimpleIntegerProperty(); - private final ModelWrapper contactWrapper = new ModelWrapper<>(); + private Contact contact; public MasterTableViewModel(Contact contact) { - id = contact.getId(); - contactWrapper.set(contact); - contactWrapper.reload(); - - if (contact.getBirthday() != null) { - age.set((int) ChronoUnit.YEARS.between(contact.getBirthday(), LocalDate.now(CentralClock.getClock()))); - } - } - - @Override - public boolean equals(Object obj) { - - if (obj == null) { - return false; - } - - if (obj == this) { - return true; - } - - if (!(obj instanceof MasterTableViewModel)) { - return false; - } - - MasterTableViewModel other = (MasterTableViewModel) obj; - - return other.getId().equals(this.getId()); - } - - @Override - public int hashCode() { - return this.getId().hashCode(); + this.contact = contact; } public String getId() { - return id; + return contact.getId(); } - public StringProperty firstnameProperty() { - return contactWrapper.field("firstname", Contact::getFirstname, Contact::setFirstname); + public String getFirstname() { + return contact.getFirstname(); } - public StringProperty lastnameProperty() { - return contactWrapper.field("lastname", Contact::getLastname, Contact::setLastname); + public String getLastname() { + return contact.getLastname(); } - public StringProperty titleProperty() { - return contactWrapper.field("title", Contact::getTitle, Contact::setTitle); + public Integer getAge(){ + if (contact.getBirthday() == null) { + return null; + } else { + return (int) ChronoUnit.YEARS.between(contact.getBirthday(), LocalDate.now(CentralClock.getClock())); + } } - public StringProperty emailAddressProperty() { - return contactWrapper.field("emailAddress", Contact::getEmailAddress, Contact::setEmailAddress); + public String getTitle() { + return contact.getTitle(); } - public IntegerProperty ageProperty() { - return age; + public String getEmailAddress() { + return contact.getEmailAddress(); } - public StringProperty cityProperty() { - return contactWrapper.field("city", - (StringGetter) model -> model.getAddress().getCity(), - (model, value) -> model.getAddress().setCity(value)); + public String getCity() { + return contact.getAddress().getCity(); } - public StringProperty streetProperty() { - return contactWrapper.field("street", - (StringGetter) model -> model.getAddress().getStreet(), - (model, value) -> model.getAddress().setStreet(value)); + public String getStreet() { + return contact.getAddress().getStreet(); } - public StringProperty postalCodeProperty() { - return contactWrapper.field("postalcode", - (StringGetter) model -> model.getAddress().getPostalcode(), - (model, value) -> model.getAddress().setPostalcode(value)); + public String getPostalCode() { + return contact.getAddress().getPostalcode(); } } diff --git a/examples/contacts-example/src/main/java/de/saxsys/mvvmfx/examples/contacts/ui/master/MasterViewModel.java b/examples/contacts-example/src/main/java/de/saxsys/mvvmfx/examples/contacts/ui/master/MasterViewModel.java index ec2b77e60..1dfd4f1bf 100644 --- a/examples/contacts-example/src/main/java/de/saxsys/mvvmfx/examples/contacts/ui/master/MasterViewModel.java +++ b/examples/contacts-example/src/main/java/de/saxsys/mvvmfx/examples/contacts/ui/master/MasterViewModel.java @@ -6,6 +6,7 @@ import javax.enterprise.event.Observes; import javax.inject.Inject; +import javax.inject.Singleton; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -23,6 +24,7 @@ import javafx.collections.FXCollections; import javafx.collections.ObservableList; +@Singleton public class MasterViewModel implements ViewModel { private static final Logger LOG = LoggerFactory.getLogger(MasterViewModel.class); @@ -33,7 +35,7 @@ public class MasterViewModel implements ViewModel { private final ObjectProperty selectedTableRow = new SimpleObjectProperty<>(); - private Optional> onSelect = Optional.empty(); + private Consumer onSelect; @Inject Repository repository; @@ -72,13 +74,10 @@ private void updateContactList() { if (selectedContactId != null) { Optional selectedRow = contacts.stream() - .filter(row -> row.getId().equals(selectedContactId)).findFirst(); + .filter(row -> row.getId().equals(selectedContactId)) + .findFirst(); - if (selectedRow.isPresent()) { - onSelect.ifPresent(consumer -> consumer.accept(selectedRow.get())); - } else { - onSelect.ifPresent(consumer -> consumer.accept(null)); - } + Optional.of(onSelect).ifPresent(consumer -> consumer.accept(selectedRow.orElse(null))); } } @@ -87,7 +86,7 @@ public ObservableList getContactList() { } public void setOnSelect(Consumer consumer) { - onSelect = Optional.of(consumer); + onSelect = consumer; } public ObjectProperty selectedTableRowProperty() { diff --git a/examples/contacts-example/src/test/java/de/saxsys/mvvmfx/examples/contacts/model/CountrySelectorIntegrationTest.java b/examples/contacts-example/src/test/java/de/saxsys/mvvmfx/examples/contacts/model/CountrySelectorIntegrationTest.java index 7be203e6f..69d283b36 100644 --- a/examples/contacts-example/src/test/java/de/saxsys/mvvmfx/examples/contacts/model/CountrySelectorIntegrationTest.java +++ b/examples/contacts-example/src/test/java/de/saxsys/mvvmfx/examples/contacts/model/CountrySelectorIntegrationTest.java @@ -9,6 +9,7 @@ import java.io.FileInputStream; import java.io.FileNotFoundException; +import java.io.InputStream; import java.util.List; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutionException; @@ -33,11 +34,11 @@ public void setup() { public void testXmlConverterForCountry() throws FileNotFoundException { XmlConverter converter = new XmlConverter<>("iso_3166_entry", Country.class); - String iso_3166_xml = this.getClass().getResource("/countries/iso_3166.xml").getFile(); + InputStream iso_3166_xml = this.getClass().getResourceAsStream("/countries/iso_3166.xml"); assertThat(iso_3166_xml).isNotNull(); - converter.initialize(new FileInputStream(iso_3166_xml)); + converter.initialize(iso_3166_xml); Country country = converter.get(); assertThat(country).isNotNull(); @@ -48,11 +49,11 @@ public void testXmlConverterForSubdivision() throws Exception { XmlConverter converter = new XmlConverter<>("iso_3166_country", CountrySelector.ISO3166_2_CountryEntity.class); - String iso_3166_2_xml = this.getClass().getResource("/countries/iso_3166_2.xml").getFile(); + InputStream iso_3166_2_xml = this.getClass().getResourceAsStream("/countries/iso_3166_2.xml"); assertThat(iso_3166_2_xml).isNotNull(); - converter.initialize(new FileInputStream(iso_3166_2_xml)); + converter.initialize(iso_3166_2_xml); CountrySelector.ISO3166_2_CountryEntity entity = converter.get(); diff --git a/examples/contacts-example/src/test/java/de/saxsys/mvvmfx/examples/contacts/ui/master/MasterTableViewModelTest.java b/examples/contacts-example/src/test/java/de/saxsys/mvvmfx/examples/contacts/ui/master/MasterTableViewModelTest.java index 5d09ac2b0..f74bab81a 100644 --- a/examples/contacts-example/src/test/java/de/saxsys/mvvmfx/examples/contacts/ui/master/MasterTableViewModelTest.java +++ b/examples/contacts-example/src/test/java/de/saxsys/mvvmfx/examples/contacts/ui/master/MasterTableViewModelTest.java @@ -28,7 +28,7 @@ public void testCalculationOfAge() { MasterTableViewModel tableViewModel = new MasterTableViewModel(contact); - assertThat(tableViewModel.ageProperty().get()).isEqualTo(22); + assertThat(tableViewModel.getAge()).isEqualTo(22); } } diff --git a/examples/contacts-example/src/test/java/de/saxsys/mvvmfx/examples/contacts/ui/master/MasterViewModelTest.java b/examples/contacts-example/src/test/java/de/saxsys/mvvmfx/examples/contacts/ui/master/MasterViewModelTest.java index add9a8ca7..6ab0f089c 100644 --- a/examples/contacts-example/src/test/java/de/saxsys/mvvmfx/examples/contacts/ui/master/MasterViewModelTest.java +++ b/examples/contacts-example/src/test/java/de/saxsys/mvvmfx/examples/contacts/ui/master/MasterViewModelTest.java @@ -111,6 +111,8 @@ public void testUpdateContactListSelectionPersistsAfterUpdate() { assertThat(getContactIdsInTable()).contains(contact2.getId(), contact3.getId()) .doesNotContain(contact1.getId()); + row2 = findTableViewModelForContact(contact2); + verify(onSelectConsumer).accept(row2); } diff --git a/examples/mini-examples/async-todoapp-futures/pom.xml b/examples/mini-examples/async-todoapp-futures/pom.xml new file mode 100644 index 000000000..58ce74cb0 --- /dev/null +++ b/examples/mini-examples/async-todoapp-futures/pom.xml @@ -0,0 +1,30 @@ + + + + mini-examples + de.saxsys.mvvmfx + 1.6.0 + + 4.0.0 + + async-todoapp-futures + + + + de.saxsys + mvvmfx + + + de.saxsys + mvvmfx-easydi + + + org.fxmisc.easybind + easybind + 1.0.3 + + + + \ No newline at end of file diff --git a/examples/mini-examples/async-todoapp-futures/src/main/java/de/saxsys/mvvmfx/examples/async_todoapp_futures/App.java b/examples/mini-examples/async-todoapp-futures/src/main/java/de/saxsys/mvvmfx/examples/async_todoapp_futures/App.java new file mode 100644 index 000000000..e54914a69 --- /dev/null +++ b/examples/mini-examples/async-todoapp-futures/src/main/java/de/saxsys/mvvmfx/examples/async_todoapp_futures/App.java @@ -0,0 +1,38 @@ +package de.saxsys.mvvmfx.examples.async_todoapp_futures; + +import de.saxsys.mvvmfx.FluentViewLoader; +import de.saxsys.mvvmfx.easydi.MvvmfxEasyDIApplication; +import de.saxsys.mvvmfx.examples.async_todoapp_futures.model.TodoItemService; +import de.saxsys.mvvmfx.examples.async_todoapp_futures.model.TodoItemServiceImpl; +import de.saxsys.mvvmfx.examples.async_todoapp_futures.ui.MainView; +import de.saxsys.mvvmfx.examples.async_todoapp_futures.ui.TodoScope; +import eu.lestard.easydi.EasyDI; +import javafx.scene.Parent; +import javafx.scene.Scene; +import javafx.stage.Stage; + +public class App extends MvvmfxEasyDIApplication { + public static void main(String[] args) { + launch(args); + } + + @Override + protected void initEasyDi(EasyDI context) throws Exception { + context.bindInterface(TodoItemService.class, TodoItemServiceImpl.class); + } + + @Override + public void startMvvmfx(Stage stage) throws Exception { + stage.setTitle("TodoApp with CompletableFutures"); + + TodoScope todoScope = new TodoScope(); + + final Parent view = FluentViewLoader + .fxmlView(MainView.class) + .providedScopes(todoScope) + .load() + .getView(); + stage.setScene(new Scene(view)); + stage.show(); + } +} diff --git a/examples/mini-examples/async-todoapp-futures/src/main/java/de/saxsys/mvvmfx/examples/async_todoapp_futures/model/TodoItem.java b/examples/mini-examples/async-todoapp-futures/src/main/java/de/saxsys/mvvmfx/examples/async_todoapp_futures/model/TodoItem.java new file mode 100644 index 000000000..96e302822 --- /dev/null +++ b/examples/mini-examples/async-todoapp-futures/src/main/java/de/saxsys/mvvmfx/examples/async_todoapp_futures/model/TodoItem.java @@ -0,0 +1,37 @@ +package de.saxsys.mvvmfx.examples.async_todoapp_futures.model; + +import java.util.Objects; +import java.util.UUID; + +public class TodoItem { + + private final String id; + + private String text; + + public TodoItem(String text) { + this.id = UUID.randomUUID().toString(); + this.text = text; + } + + public String getId() { + return id; + } + + public String getText() { + return text; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + TodoItem todoItem = (TodoItem) o; + return Objects.equals(id, todoItem.id); + } + + @Override + public int hashCode() { + return Objects.hash(id); + } +} diff --git a/examples/mini-examples/async-todoapp-futures/src/main/java/de/saxsys/mvvmfx/examples/async_todoapp_futures/model/TodoItemService.java b/examples/mini-examples/async-todoapp-futures/src/main/java/de/saxsys/mvvmfx/examples/async_todoapp_futures/model/TodoItemService.java new file mode 100644 index 000000000..1acd42070 --- /dev/null +++ b/examples/mini-examples/async-todoapp-futures/src/main/java/de/saxsys/mvvmfx/examples/async_todoapp_futures/model/TodoItemService.java @@ -0,0 +1,18 @@ +package de.saxsys.mvvmfx.examples.async_todoapp_futures.model; + + +import javax.inject.Singleton; +import java.util.List; + +@Singleton +public interface TodoItemService { + + void createItem(TodoItem item); + + void deleteItem(TodoItem item); + + TodoItem getItemById(String id); + + List getAllItems(); + +} \ No newline at end of file diff --git a/examples/mini-examples/async-todoapp-futures/src/main/java/de/saxsys/mvvmfx/examples/async_todoapp_futures/model/TodoItemServiceImpl.java b/examples/mini-examples/async-todoapp-futures/src/main/java/de/saxsys/mvvmfx/examples/async_todoapp_futures/model/TodoItemServiceImpl.java new file mode 100644 index 000000000..a930b4f8b --- /dev/null +++ b/examples/mini-examples/async-todoapp-futures/src/main/java/de/saxsys/mvvmfx/examples/async_todoapp_futures/model/TodoItemServiceImpl.java @@ -0,0 +1,68 @@ +package de.saxsys.mvvmfx.examples.async_todoapp_futures.model; + + +import javax.inject.Singleton; +import java.util.ArrayList; +import java.util.List; + +@Singleton +public class TodoItemServiceImpl implements TodoItemService { + + /* + * To simulate a "real" backend service, this constant defines + * the amount of time that this service will take to answer requests. + */ + private final static int DELAY_IN_MS = 400; + + private List items = new ArrayList<>(); + + @Override + public void createItem(TodoItem item) { + delay(); + throwRandomError(); + if(!items.contains(item)) { + items.add(item); + } + } + + @Override + public void deleteItem(TodoItem item) { + delay(); + throwRandomError(); + items.remove(item); + } + + @Override + public TodoItem getItemById(String id) { + delay(); + throwRandomError(); + return items.stream() + .filter(item -> item.getId().equals(id)) + .findFirst() + .orElse(null); + } + + @Override + public List getAllItems() { + delay(); + return new ArrayList<>(items); + } + + private void delay() { + if(DELAY_IN_MS > 0) { + try{ + Thread.sleep(DELAY_IN_MS); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + } + + private void throwRandomError() { + final double rand = Math.random(); + + if(rand > 0.8) { + throw new RuntimeException("Connection Timeout"); + } + } +} diff --git a/examples/mini-examples/async-todoapp-futures/src/main/java/de/saxsys/mvvmfx/examples/async_todoapp_futures/ui/ControlsView.java b/examples/mini-examples/async-todoapp-futures/src/main/java/de/saxsys/mvvmfx/examples/async_todoapp_futures/ui/ControlsView.java new file mode 100644 index 000000000..826b57295 --- /dev/null +++ b/examples/mini-examples/async-todoapp-futures/src/main/java/de/saxsys/mvvmfx/examples/async_todoapp_futures/ui/ControlsView.java @@ -0,0 +1,35 @@ +package de.saxsys.mvvmfx.examples.async_todoapp_futures.ui; + +import de.saxsys.mvvmfx.FxmlView; +import de.saxsys.mvvmfx.InjectViewModel; +import javafx.fxml.FXML; +import javafx.scene.control.Button; +import javafx.scene.control.TextField; + +public class ControlsView implements FxmlView { + + @FXML + public TextField input; + @FXML + public Button addButton; + @FXML + public Button removeButton; + + @InjectViewModel + private ControlsViewModel viewModel; + + public void initialize() { + input.textProperty().bindBidirectional(viewModel.inputProperty()); + + addButton.disableProperty().bind(viewModel.addPossibleProperty().not()); + removeButton.disableProperty().bind(viewModel.deletePossibleProperty().not()); + } + + public void add() { + viewModel.addItem(); + } + + public void remove() { + viewModel.removeItem(); + } +} \ No newline at end of file diff --git a/examples/mini-examples/async-todoapp-futures/src/main/java/de/saxsys/mvvmfx/examples/async_todoapp_futures/ui/ControlsViewModel.java b/examples/mini-examples/async-todoapp-futures/src/main/java/de/saxsys/mvvmfx/examples/async_todoapp_futures/ui/ControlsViewModel.java new file mode 100644 index 000000000..8ef610a3f --- /dev/null +++ b/examples/mini-examples/async-todoapp-futures/src/main/java/de/saxsys/mvvmfx/examples/async_todoapp_futures/ui/ControlsViewModel.java @@ -0,0 +1,92 @@ +package de.saxsys.mvvmfx.examples.async_todoapp_futures.ui; + +import de.saxsys.mvvmfx.InjectScope; +import de.saxsys.mvvmfx.ViewModel; +import de.saxsys.mvvmfx.examples.async_todoapp_futures.model.TodoItem; +import de.saxsys.mvvmfx.examples.async_todoapp_futures.model.TodoItemService; +import javafx.beans.binding.Bindings; +import javafx.beans.property.*; + +import java.util.concurrent.CompletableFuture; + +public class ControlsViewModel implements ViewModel { + + private StringProperty input = new SimpleStringProperty(); + + private ReadOnlyBooleanWrapper addPossible = new ReadOnlyBooleanWrapper(); + private ReadOnlyBooleanWrapper deletePossible = new ReadOnlyBooleanWrapper(); + + private BooleanProperty uiDisabled = new SimpleBooleanProperty(false); + + @InjectScope + private TodoScope todoScope; + + private TodoItemService itemService; + + public ControlsViewModel(TodoItemService itemService) { + this.itemService = itemService; + } + + public void initialize() { + deletePossible.bind(Bindings.and(todoScope.selectedItemProperty().isNotNull(), uiDisabled.not())); + addPossible.bind(Bindings.and( + Bindings.and( + input.isNotNull(), + input.isNotEmpty()), + uiDisabled.not())); + } + + public StringProperty inputProperty() { + return input; + } + + public ReadOnlyBooleanProperty addPossibleProperty() { + return addPossible.getReadOnlyProperty(); + } + + public ReadOnlyBooleanProperty deletePossibleProperty() { + return deletePossible.getReadOnlyProperty(); + } + + public void addItem() { + final String inputValue = input.getValue(); + + if(inputValue != null && !inputValue.trim().isEmpty()) { + if(!uiDisabled.get()) { + CompletableFuture + .runAsync(() -> { + uiDisabled.setValue(true); + itemService.createItem(new TodoItem(input.get())); + }) + .thenRun(() -> { + uiDisabled.setValue(false); + input.setValue(""); + todoScope.publish(TodoScope.UPDATE_MSG); + todoScope.setError(null); + }).exceptionally(throwable -> { + uiDisabled.setValue(false); + todoScope.setError(throwable.getCause()); + return null; + }); + } + } + } + + public void removeItem() { + if(deletePossible.get() && !uiDisabled.get()) { + CompletableFuture.runAsync(() -> { + uiDisabled.setValue(true); + itemService.deleteItem(todoScope.selectedItemProperty().get()); + }).thenRun(() -> { + uiDisabled.setValue(false); + todoScope.selectedItemProperty().setValue(null); + todoScope.publish(TodoScope.UPDATE_MSG); + todoScope.setError(null); + }).exceptionally(throwable -> { + uiDisabled.setValue(false); + todoScope.setError(throwable.getCause()); + return null; + }); + } + } +} \ No newline at end of file diff --git a/examples/mini-examples/async-todoapp-futures/src/main/java/de/saxsys/mvvmfx/examples/async_todoapp_futures/ui/ItemListView.java b/examples/mini-examples/async-todoapp-futures/src/main/java/de/saxsys/mvvmfx/examples/async_todoapp_futures/ui/ItemListView.java new file mode 100644 index 000000000..9b9762d81 --- /dev/null +++ b/examples/mini-examples/async-todoapp-futures/src/main/java/de/saxsys/mvvmfx/examples/async_todoapp_futures/ui/ItemListView.java @@ -0,0 +1,46 @@ +package de.saxsys.mvvmfx.examples.async_todoapp_futures.ui; + +import de.saxsys.mvvmfx.FxmlView; +import de.saxsys.mvvmfx.InjectViewModel; +import de.saxsys.mvvmfx.examples.async_todoapp_futures.model.TodoItem; +import javafx.fxml.FXML; +import javafx.scene.control.ListCell; +import javafx.scene.control.ListView; +import javafx.util.Callback; + +public class ItemListView implements FxmlView { + + @FXML + public ListView items; + + @InjectViewModel + private ItemListViewModel viewModel; + + public void initialize() { + items.setCellFactory(new Callback, ListCell>() { + @Override + public ListCell call(ListView param) { + return new ListCell(){ + @Override + protected void updateItem(TodoItem item, boolean empty) { + super.updateItem(item, empty); + if(item != null) { + setText(item.getText()); + } else { + setText(""); + } + } + }; + } + }); + + items.setItems(viewModel.itemsProperty()); + items.getSelectionModel().selectedItemProperty().addListener(((observable, oldValue, newValue) -> { + viewModel.selectedItemProperty().setValue(newValue); + })); + + viewModel.selectedItemProperty().addListener(((observable, oldValue, newValue) -> { + items.getSelectionModel().select(newValue); + })); + } +} \ No newline at end of file diff --git a/examples/mini-examples/async-todoapp-futures/src/main/java/de/saxsys/mvvmfx/examples/async_todoapp_futures/ui/ItemListViewModel.java b/examples/mini-examples/async-todoapp-futures/src/main/java/de/saxsys/mvvmfx/examples/async_todoapp_futures/ui/ItemListViewModel.java new file mode 100644 index 000000000..64e0d3cf9 --- /dev/null +++ b/examples/mini-examples/async-todoapp-futures/src/main/java/de/saxsys/mvvmfx/examples/async_todoapp_futures/ui/ItemListViewModel.java @@ -0,0 +1,58 @@ +package de.saxsys.mvvmfx.examples.async_todoapp_futures.ui; + +import de.saxsys.mvvmfx.InjectScope; +import de.saxsys.mvvmfx.ViewModel; +import de.saxsys.mvvmfx.examples.async_todoapp_futures.model.TodoItem; +import de.saxsys.mvvmfx.examples.async_todoapp_futures.model.TodoItemService; +import javafx.application.Platform; +import javafx.beans.property.ObjectProperty; +import javafx.beans.property.SimpleObjectProperty; +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; + +import java.util.Collections; +import java.util.concurrent.CompletableFuture; + +public class ItemListViewModel implements ViewModel { + + private ObservableList items = FXCollections.observableArrayList(); + private ObjectProperty selectedItem = new SimpleObjectProperty<>(); + + @InjectScope + private TodoScope todoScope; + + private TodoItemService itemService; + + public ItemListViewModel(TodoItemService itemService) { + this.itemService = itemService; + } + + public void initialize() { + todoScope.subscribe(TodoScope.UPDATE_MSG, (k,v) -> update()); + update(); + + todoScope.selectedItemProperty().bindBidirectional(selectedItem); + } + + private void update() { + CompletableFuture.supplyAsync(() -> { + return itemService.getAllItems(); + }).thenAccept(allItems -> { + Collections.reverse(allItems); + Platform.runLater(() -> items.setAll(allItems)); + todoScope.setError(null); + }).exceptionally(throwable -> { + todoScope.setError(throwable.getCause()); + return null; + }); + } + + public ObservableList itemsProperty() { + return items; + } + + public ObjectProperty selectedItemProperty() { + return selectedItem; + } + +} diff --git a/examples/mini-examples/async-todoapp-futures/src/main/java/de/saxsys/mvvmfx/examples/async_todoapp_futures/ui/ListItem.java b/examples/mini-examples/async-todoapp-futures/src/main/java/de/saxsys/mvvmfx/examples/async_todoapp_futures/ui/ListItem.java new file mode 100644 index 000000000..84b8bbc85 --- /dev/null +++ b/examples/mini-examples/async-todoapp-futures/src/main/java/de/saxsys/mvvmfx/examples/async_todoapp_futures/ui/ListItem.java @@ -0,0 +1,50 @@ +package de.saxsys.mvvmfx.examples.async_todoapp_futures.ui; + +import javafx.beans.property.SimpleStringProperty; +import javafx.beans.property.StringProperty; + +import java.util.Objects; + +public class ListItem { + + private final ID id; + private StringProperty label = new SimpleStringProperty(); + + public ListItem(ID id) { + this.id = id; + } + + public ListItem(ID id, String label) { + this(id); + this.label.setValue(label); + } + + public ID getId() { + return id; + } + + public String getLabel() { + return label.get(); + } + + public StringProperty labelProperty() { + return label; + } + + public void setLabel(String label) { + this.label.set(label); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + ListItem listItem = (ListItem) o; + return Objects.equals(id, listItem.id); + } + + @Override + public int hashCode() { + return Objects.hash(id); + } +} diff --git a/examples/mini-examples/async-todoapp-futures/src/main/java/de/saxsys/mvvmfx/examples/async_todoapp_futures/ui/MainView.java b/examples/mini-examples/async-todoapp-futures/src/main/java/de/saxsys/mvvmfx/examples/async_todoapp_futures/ui/MainView.java new file mode 100644 index 000000000..938ff83f2 --- /dev/null +++ b/examples/mini-examples/async-todoapp-futures/src/main/java/de/saxsys/mvvmfx/examples/async_todoapp_futures/ui/MainView.java @@ -0,0 +1,21 @@ +package de.saxsys.mvvmfx.examples.async_todoapp_futures.ui; + +import de.saxsys.mvvmfx.FxmlView; +import de.saxsys.mvvmfx.InjectViewModel; +import javafx.fxml.FXML; +import javafx.scene.control.Label; + +public class MainView implements FxmlView { + + @FXML + public Label errorLabel; + + @InjectViewModel + private MainViewModel viewModel; + + public void initialize() { + errorLabel.textProperty().bind(viewModel.errorTextProperty()); + + errorLabel.visibleProperty().bind(viewModel.errorVisibleProperty()); + } +} diff --git a/examples/mini-examples/async-todoapp-futures/src/main/java/de/saxsys/mvvmfx/examples/async_todoapp_futures/ui/MainViewModel.java b/examples/mini-examples/async-todoapp-futures/src/main/java/de/saxsys/mvvmfx/examples/async_todoapp_futures/ui/MainViewModel.java new file mode 100644 index 000000000..1e3a79eae --- /dev/null +++ b/examples/mini-examples/async-todoapp-futures/src/main/java/de/saxsys/mvvmfx/examples/async_todoapp_futures/ui/MainViewModel.java @@ -0,0 +1,42 @@ +package de.saxsys.mvvmfx.examples.async_todoapp_futures.ui; + +import javafx.beans.binding.Bindings; +import javafx.beans.property.ReadOnlyBooleanProperty; +import javafx.beans.property.ReadOnlyBooleanWrapper; +import javafx.beans.property.ReadOnlyStringProperty; +import javafx.beans.property.ReadOnlyStringWrapper; + +import de.saxsys.mvvmfx.InjectScope; +import de.saxsys.mvvmfx.ViewModel; + + +public class MainViewModel implements ViewModel { + + @InjectScope + private TodoScope todoScope; + + private ReadOnlyStringWrapper errorText = new ReadOnlyStringWrapper(); + private ReadOnlyBooleanWrapper errorVisible = new ReadOnlyBooleanWrapper(); + + public void initialize() { + errorText.bind(Bindings.createStringBinding(() -> { + final Throwable throwable = todoScope.errorProperty().getValue(); + + if(throwable != null) { + return throwable.getLocalizedMessage(); + } else { + return ""; + } + }, todoScope.errorProperty())); + + errorVisible.bind(todoScope.errorProperty().isNotNull()); + } + + public ReadOnlyStringProperty errorTextProperty() { + return errorText.getReadOnlyProperty(); + } + + public ReadOnlyBooleanProperty errorVisibleProperty() { + return errorVisible.getReadOnlyProperty(); + } +} diff --git a/examples/mini-examples/async-todoapp-futures/src/main/java/de/saxsys/mvvmfx/examples/async_todoapp_futures/ui/TodoScope.java b/examples/mini-examples/async-todoapp-futures/src/main/java/de/saxsys/mvvmfx/examples/async_todoapp_futures/ui/TodoScope.java new file mode 100644 index 000000000..b90b33b96 --- /dev/null +++ b/examples/mini-examples/async-todoapp-futures/src/main/java/de/saxsys/mvvmfx/examples/async_todoapp_futures/ui/TodoScope.java @@ -0,0 +1,29 @@ +package de.saxsys.mvvmfx.examples.async_todoapp_futures.ui; + +import de.saxsys.mvvmfx.Scope; +import de.saxsys.mvvmfx.examples.async_todoapp_futures.model.TodoItem; +import javafx.application.Platform; +import javafx.beans.property.ObjectProperty; +import javafx.beans.property.ReadOnlyObjectProperty; +import javafx.beans.property.ReadOnlyObjectWrapper; +import javafx.beans.property.SimpleObjectProperty; + +public class TodoScope implements Scope { + + public static final String UPDATE_MSG = "TodoScope.UPDATE_MSG"; + + private ObjectProperty error = new SimpleObjectProperty<>(); + private ReadOnlyObjectWrapper selectedItem = new ReadOnlyObjectWrapper<>(); + + public ObjectProperty selectedItemProperty() { + return selectedItem; + } + + public void setError(Throwable throwable) { + Platform.runLater(() -> error.setValue(throwable)); + } + + public ReadOnlyObjectProperty errorProperty() { + return error; + } +} diff --git a/examples/mini-examples/async-todoapp-futures/src/main/resources/de/saxsys/mvvmfx/examples/async_todoapp_futures/ui/ControlsView.fxml b/examples/mini-examples/async-todoapp-futures/src/main/resources/de/saxsys/mvvmfx/examples/async_todoapp_futures/ui/ControlsView.fxml new file mode 100644 index 000000000..052b67a13 --- /dev/null +++ b/examples/mini-examples/async-todoapp-futures/src/main/resources/de/saxsys/mvvmfx/examples/async_todoapp_futures/ui/ControlsView.fxml @@ -0,0 +1,16 @@ + + + + + + + + + + +