diff --git a/README.md b/README.md index 957f7a269..ad71a3af8 100644 --- a/README.md +++ b/README.md @@ -4,8 +4,10 @@ __mvvm(fx)__ is an application framework which provides you necessary components __MVVM__ is the enhanced version of the [Presentation Model](http://martinfowler.com/eaaDev/PresentationModel.html "Presentation Model") pattern and was created by Microsoft engineers for [WPF](http://msdn.microsoft.com/en-us/library/ms754130.aspx "WPF") . JavaFX and WPF does have similarities like Databinding and descriptive UI declaration (FXML/XAML). Because of this fact we adopt best practices of the development with the Microsoft technology. +[![Commercial Support](https://img.shields.io/badge/Commercial%20Support%20-by%20Saxonia%20Systems-brightgreen.svg)](http://goo.gl/forms/WVBG3SWHuL) [![Build Status](https://api.travis-ci.org/sialcasa/mvvmFX.svg?branch=develop)](https://travis-ci.org/sialcasa/mvvmFX) + ###[Howto](../../wiki "Howto")### ### Maven dependency### @@ -18,7 +20,7 @@ This is the stable release that can be used in production. de.saxsys mvvmfx - 1.4.1 + 1.5.0 ``` @@ -30,7 +32,7 @@ Here we make bug fixes for the current stable release. de.saxsys mvvmfx - 1.4.2-SNAPSHOT + 1.5.1-SNAPSHOT ``` @@ -38,11 +40,11 @@ Here we make bug fixes for the current stable release. Here we develop new features. This release is unstable and shouldn't be used in production. -``` +```xml de.saxsys mvvmfx - 1.5.0-SNAPSHOT + 1.6.0-SNAPSHOT ``` diff --git a/examples/books-example/pom.xml b/examples/books-example/pom.xml index 6dfa7867f..bbe285bc4 100644 --- a/examples/books-example/pom.xml +++ b/examples/books-example/pom.xml @@ -5,7 +5,7 @@ de.saxsys.mvvmfx examples - 1.4.1 + 1.5.0 4.0.0 @@ -17,6 +17,18 @@ 1.8 + + + + + src/main/java + + + src/main/resources + + + + de.saxsys diff --git a/examples/books-example/src/main/resources/de/saxsys/mvvmfx/examples/books/BookListItemView.fxml b/examples/books-example/src/main/java/de/saxsys/mvvmfx/examples/books/BookListItemView.fxml similarity index 100% rename from examples/books-example/src/main/resources/de/saxsys/mvvmfx/examples/books/BookListItemView.fxml rename to examples/books-example/src/main/java/de/saxsys/mvvmfx/examples/books/BookListItemView.fxml diff --git a/examples/books-example/src/main/resources/de/saxsys/mvvmfx/examples/books/MainView.fxml b/examples/books-example/src/main/java/de/saxsys/mvvmfx/examples/books/MainView.fxml similarity index 100% rename from examples/books-example/src/main/resources/de/saxsys/mvvmfx/examples/books/MainView.fxml rename to examples/books-example/src/main/java/de/saxsys/mvvmfx/examples/books/MainView.fxml diff --git a/examples/books-example/src/main/resources/de/saxsys/mvvmfx/examples/books/style.css b/examples/books-example/src/main/java/de/saxsys/mvvmfx/examples/books/style.css similarity index 100% rename from examples/books-example/src/main/resources/de/saxsys/mvvmfx/examples/books/style.css rename to examples/books-example/src/main/java/de/saxsys/mvvmfx/examples/books/style.css diff --git a/examples/contacts-example/README.md b/examples/contacts-example/README.md index 8d63549c4..4cb120846 100644 --- a/examples/contacts-example/README.md +++ b/examples/contacts-example/README.md @@ -22,34 +22,69 @@ With a dialog you can add new contacts or edit existing ones. ### Highlights and interesting parts -#### Dialogs opened with CDI-Events +#### Usage of Scopes -- The application uses CDI-Events to decouple the *add*/*edit* dialogs from the places where they are opened. Instead, when a - button is clicked to open a dialog, an CDI-Event is fired. The dialog reacts to this event and will open up itself. +[mvvmFX Scopes](https://github.com/sialcasa/mvvmFX/wiki/Scopes) are used for two scenarios in this example: -[ToolbarViewModel.java:](src/main/java/de/saxsys/mvvmfx/examples/contacts/ui/toolbar/ToolbarViewModel.java) +* Communication between the Master and the Detail View +* Dialog to add or edit a contact -```java -@Inject -private Event openPopupEvent; +##### Scope for Master Detail View + +In this scenario the [MasterDetailScope](src/main/java/de/saxsys/mvvmfx/examples/contacts/ui/scopes/MasterDetailScope.java) is used to provide a property where the [MasterViewModel.java](src/main/java/de/saxsys/mvvmfx/examples/contacts/ui/master/MasterViewModel.java) signals which contact should be displayed in the [DetailViewModel.java](src/main/java/de/saxsys/mvvmfx/examples/contacts/ui/detail/DetailViewModel.java). -public void addNewContactAction(){ - openPopupEvent.fire(new OpenAddContactDialogEvent()); +```Java +public class MasterDetailScope implements Scope { + private final ObjectProperty selectedContact = new SimpleObjectProperty<>(this, "selectedContact"); } ``` -[AddContactDialog.java:](src/main/java/de/saxsys/mvvmfx/examples/contacts/ui/addcontact/AddContactDialog.java) - -```java -public class AddContactDialog implements FxmlView { - ... +```Java +public class MasterViewModel implements ViewModel { + @InjectScope + MasterDetailScope mdScope; + + public void initialize() { + mdScope.selectedContactProperty().bind(selectedContact); + } +} +``` - public void open(@Observes OpenAddContactDialogEvent event) { - viewModel.openDialog(); - } +```Java +public class DetailViewModel implements ViewModel { + @InjectScope + MasterDetailScope mdScope; + + ...//Create all bindings to the information of the mdScope.selectedContactProperty() } + ``` + +##### Scope for Dialog Wizard + +In this case the scope is used to handle the state of a multi paged dialog. + +To understand the machanism of the implemented dialogs, you should check the following classes: + +* [EditContactDialogView](src/main/java/de/saxsys/mvvmfx/examples/contacts/ui/editcontact/EditContactDialog.java) and [AddContactDialog](src/main/java/de/saxsys/mvvmfx/examples/contacts/ui/addcontact/AddContactDialog.java) + +* Both of them are using the [ContactDialogView](src/main/java/de/saxsys/mvvmfx/examples/contacts/ui/contactdialog/ContactDialogView.java) with different configurations. + +* [AddressFormViewModel](src/main/java/de/saxsys/mvvmfx/examples/contacts/ui/addressform/AddressFormViewModel.java) and [ContactFormViewModel](src/main/java/de/saxsys/mvvmfx/examples/contacts/ui/contactform/ContactFormViewModel.java) are the dialog pages (1 and 2) that are displayed in the EditContactDialog and the AddContactDialog + +* Also check the Views where the dialogs are created ([ToolbarView.java](src/main/java/de/saxsys/mvvmfx/examples/contacts/ui/toolbar/ToolbarView) and [DetailView](src/main/java/de/saxsys/mvvmfx/examples/contacts/ui/detail/DetailView.java)). + +######Scope usages###### +The used scope is called [ContactDialogScope](src/main/java/de/saxsys/mvvmfx/examples/contacts/ui/scopes/ContactDialogScope.java) and it has three use cases: + +1. Configuration (eg. title) of the [ContactDialogViewModel](src/main/java/de/saxsys/mvvmfx/examples/contacts/ui/contactdialog/ContactDialogViewModel.java) from the [EditContactDialogViewModel](src/main/java/de/saxsys/mvvmfx/examples/contacts/ui/editcontact/EditContactDialogViewModel.java) and [AddContactDialogViewModel](src/main/java/de/saxsys/mvvmfx/examples/contacts/ui/addcontact/AddContactDialogViewModel.java). + +2. [DetailViewModel](src/main/java/de/saxsys/mvvmfx/examples/contacts/ui/detail/DetailViewModel) sets the Contact object that will be edited into the scope. This information is used by the dialog pages: [AddressFormViewModel](src/main/java/de/saxsys/mvvmfx/examples/contacts/ui/addressform/AddressFormViewModel.java) and [ContactFormViewModel](src/main/java/de/saxsys/mvvmfx/examples/contacts/ui/contactform/ContactFormViewModel.java) + +3. [ContactDialogViewModel](src/main/java/de/saxsys/mvvmfx/examples/contacts/ui/contactdialog/ContactDialogViewModel.java) binds the *disableProperty()* of the navigation buttons to the validation state in the scope. This validation state is bound to the validation state of the dialog pages (AddressFormView and ContactFormView). + + #### ResourceBundles and I18N There are resourceBundles available for german and english language. In [App.java](src/main/java/de/saxsys/mvvmfx/examples/contacts/App.java) diff --git a/examples/contacts-example/pom.xml b/examples/contacts-example/pom.xml index ad6855b22..654c2b88b 100644 --- a/examples/contacts-example/pom.xml +++ b/examples/contacts-example/pom.xml @@ -6,7 +6,7 @@ de.saxsys.mvvmfx examples - 1.4.1 + 1.5.0 contacts-example @@ -27,8 +27,18 @@ + + + + src/main/java + + + src/main/resources + + + de.saxsys @@ -106,11 +116,11 @@ test - - de.saxsys - mvvmfx-testing-utils - test - + + de.saxsys + mvvmfx-testing-utils + test + \ No newline at end of file diff --git a/examples/contacts-example/src/main/java/de/saxsys/mvvmfx/examples/contacts/App.java b/examples/contacts-example/src/main/java/de/saxsys/mvvmfx/examples/contacts/App.java index 543131ecf..6feb8caf5 100644 --- a/examples/contacts-example/src/main/java/de/saxsys/mvvmfx/examples/contacts/App.java +++ b/examples/contacts-example/src/main/java/de/saxsys/mvvmfx/examples/contacts/App.java @@ -24,23 +24,22 @@ import de.saxsys.mvvmfx.examples.contacts.ui.main.MainViewModel; public class App extends MvvmfxCdiApplication { - + private static final Logger LOG = LoggerFactory.getLogger(App.class); - + public static void main(String... args) { - + Locale.setDefault(Locale.ENGLISH); - + launch(args); } - - + @Inject private ResourceBundle resourceBundle; - + @Inject private Repository repository; - + @Override public void initMvvmfx() throws Exception { int numberOfContacts = 30; @@ -48,30 +47,31 @@ public void initMvvmfx() throws Exception { repository.save(ContactFactory.createRandomContact()); } } - + @Override public void startMvvmfx(Stage stage) throws Exception { LOG.info("Starting the Application"); MvvmFX.setGlobalResourceBundle(resourceBundle); - + stage.setTitle(resourceBundle.getString("window.title")); - + ViewTuple main = FluentViewLoader.fxmlView(MainView.class).load(); - - + Scene rootScene = new Scene(main.getView()); - + rootScene.getStylesheets().add("/contacts.css"); - + stage.setScene(rootScene); stage.show(); } - + /** - * The shutdown of the application can be triggered by firing the {@link TriggerShutdownEvent} CDI event. + * The shutdown of the application can be triggered by firing the + * {@link TriggerShutdownEvent} CDI event. */ public void triggerShutdown(@Observes TriggerShutdownEvent event) { LOG.info("Application will now shut down"); Platform.exit(); } + } diff --git a/examples/contacts-example/src/main/java/de/saxsys/mvvmfx/examples/contacts/config/ResourceProvider.java b/examples/contacts-example/src/main/java/de/saxsys/mvvmfx/examples/contacts/config/ResourceProvider.java index ccef780d2..ef499e696 100644 --- a/examples/contacts-example/src/main/java/de/saxsys/mvvmfx/examples/contacts/config/ResourceProvider.java +++ b/examples/contacts-example/src/main/java/de/saxsys/mvvmfx/examples/contacts/config/ResourceProvider.java @@ -5,15 +5,16 @@ import java.util.ResourceBundle; /** - * A singleton CDI provider that is used to load the resource bundle and provide it for the CDI injection. + * A singleton CDI provider that is used to load the resource bundle and provide + * it for the CDI injection. */ @Singleton public class ResourceProvider { - + /* * Due to the @Produces annotation this resource bundle can be injected in all views. */ @Produces private ResourceBundle defaultResourceBundle = ResourceBundle.getBundle("default"); - + } diff --git a/examples/contacts-example/src/main/java/de/saxsys/mvvmfx/examples/contacts/events/ContactsUpdatedEvent.java b/examples/contacts-example/src/main/java/de/saxsys/mvvmfx/examples/contacts/events/ContactsUpdatedEvent.java index a273c5b6e..92cfd8f11 100644 --- a/examples/contacts-example/src/main/java/de/saxsys/mvvmfx/examples/contacts/events/ContactsUpdatedEvent.java +++ b/examples/contacts-example/src/main/java/de/saxsys/mvvmfx/examples/contacts/events/ContactsUpdatedEvent.java @@ -1,7 +1,9 @@ package de.saxsys.mvvmfx.examples.contacts.events; /** - * CDI event class that is used to indicate that a contact was updated/added/removed. + * CDI event class that is used to indicate that a contact was + * updated/added/removed. */ public class ContactsUpdatedEvent { + } diff --git a/examples/contacts-example/src/main/java/de/saxsys/mvvmfx/examples/contacts/events/OpenAboutDialogEvent.java b/examples/contacts-example/src/main/java/de/saxsys/mvvmfx/examples/contacts/events/OpenAboutDialogEvent.java deleted file mode 100644 index b7947d747..000000000 --- a/examples/contacts-example/src/main/java/de/saxsys/mvvmfx/examples/contacts/events/OpenAboutDialogEvent.java +++ /dev/null @@ -1,9 +0,0 @@ -package de.saxsys.mvvmfx.examples.contacts.events; - -import de.saxsys.mvvmfx.examples.contacts.ui.about.AboutView; - -/** - * CDI event class that is used to indicate that the {@link AboutView} dialog should be opened. - */ -public class OpenAboutDialogEvent { -} diff --git a/examples/contacts-example/src/main/java/de/saxsys/mvvmfx/examples/contacts/events/OpenAddContactDialogEvent.java b/examples/contacts-example/src/main/java/de/saxsys/mvvmfx/examples/contacts/events/OpenAddContactDialogEvent.java deleted file mode 100644 index 085bc9022..000000000 --- a/examples/contacts-example/src/main/java/de/saxsys/mvvmfx/examples/contacts/events/OpenAddContactDialogEvent.java +++ /dev/null @@ -1,9 +0,0 @@ -package de.saxsys.mvvmfx.examples.contacts.events; - -import de.saxsys.mvvmfx.examples.contacts.ui.addcontact.AddContactDialog; - -/** - * CDI event class that is used to indicate that the {@link AddContactDialog} should be opened. - */ -public class OpenAddContactDialogEvent { -} diff --git a/examples/contacts-example/src/main/java/de/saxsys/mvvmfx/examples/contacts/events/OpenAuthorPageEvent.java b/examples/contacts-example/src/main/java/de/saxsys/mvvmfx/examples/contacts/events/OpenAuthorPageEvent.java deleted file mode 100644 index 6c8977679..000000000 --- a/examples/contacts-example/src/main/java/de/saxsys/mvvmfx/examples/contacts/events/OpenAuthorPageEvent.java +++ /dev/null @@ -1,9 +0,0 @@ -package de.saxsys.mvvmfx.examples.contacts.events; - -import de.saxsys.mvvmfx.examples.contacts.ui.about.AboutAuthorView; - -/** - * CDI event class that is used to indicate that the {@link AboutAuthorView} dialog should be opened. - */ -public class OpenAuthorPageEvent { -} diff --git a/examples/contacts-example/src/main/java/de/saxsys/mvvmfx/examples/contacts/events/OpenEditContactDialogEvent.java b/examples/contacts-example/src/main/java/de/saxsys/mvvmfx/examples/contacts/events/OpenEditContactDialogEvent.java deleted file mode 100644 index a7d736526..000000000 --- a/examples/contacts-example/src/main/java/de/saxsys/mvvmfx/examples/contacts/events/OpenEditContactDialogEvent.java +++ /dev/null @@ -1,25 +0,0 @@ -package de.saxsys.mvvmfx.examples.contacts.events; - -import de.saxsys.mvvmfx.examples.contacts.ui.editcontact.EditContactDialog; - -/** - * CDI event class that is used to indicate that the {@link EditContactDialog} dialog should be opened. - * - * It contains the id of the contact to edit. - */ -public class OpenEditContactDialogEvent { - - private final String contactId; - - /** - * @param contactId - * the id of the contact to edit. - */ - public OpenEditContactDialogEvent(String contactId) { - this.contactId = contactId; - } - - public String getContactId() { - return contactId; - } -} diff --git a/examples/contacts-example/src/main/java/de/saxsys/mvvmfx/examples/contacts/events/TriggerShutdownEvent.java b/examples/contacts-example/src/main/java/de/saxsys/mvvmfx/examples/contacts/events/TriggerShutdownEvent.java index 35ee86c8c..3574ae803 100644 --- a/examples/contacts-example/src/main/java/de/saxsys/mvvmfx/examples/contacts/events/TriggerShutdownEvent.java +++ b/examples/contacts-example/src/main/java/de/saxsys/mvvmfx/examples/contacts/events/TriggerShutdownEvent.java @@ -4,4 +4,5 @@ * Event class to trigger the shutdown of the application. */ public class TriggerShutdownEvent { + } diff --git a/examples/contacts-example/src/main/java/de/saxsys/mvvmfx/examples/contacts/events/package-info.java b/examples/contacts-example/src/main/java/de/saxsys/mvvmfx/examples/contacts/events/package-info.java index 65a9bda1a..c28f11518 100644 --- a/examples/contacts-example/src/main/java/de/saxsys/mvvmfx/examples/contacts/events/package-info.java +++ b/examples/contacts-example/src/main/java/de/saxsys/mvvmfx/examples/contacts/events/package-info.java @@ -1,4 +1,4 @@ /** - * This package contains CDI event classes. + * This package contains CDI event classes. */ -package de.saxsys.mvvmfx.examples.contacts.events; \ No newline at end of file +package de.saxsys.mvvmfx.examples.contacts.events; diff --git a/examples/contacts-example/src/main/java/de/saxsys/mvvmfx/examples/contacts/model/Address.java b/examples/contacts-example/src/main/java/de/saxsys/mvvmfx/examples/contacts/model/Address.java index 5b3addf0f..9c1d03e61 100644 --- a/examples/contacts-example/src/main/java/de/saxsys/mvvmfx/examples/contacts/model/Address.java +++ b/examples/contacts-example/src/main/java/de/saxsys/mvvmfx/examples/contacts/model/Address.java @@ -4,61 +4,59 @@ * An entity class that represents an address. */ public class Address extends Identity { - + private Country country; - + private Subdivision subdivision; - + private String street; - + private String postalcode; - + private String city; - - + Address() { + } - - + public Subdivision getSubdivision() { return subdivision; } - + public void setSubdivision(Subdivision subdivision) { this.subdivision = subdivision; } - + public Country getCountry() { return country; } - + public void setCountry(Country country) { this.country = country; } - + public String getStreet() { return street; } - + public void setStreet(String street) { this.street = street; } - + public String getPostalcode() { return postalcode; } - + public void setPostalcode(String postalcode) { this.postalcode = postalcode; } - + public String getCity() { return city; } - + public void setCity(String city) { this.city = city; } - - + } diff --git a/examples/contacts-example/src/main/java/de/saxsys/mvvmfx/examples/contacts/model/Contact.java b/examples/contacts-example/src/main/java/de/saxsys/mvvmfx/examples/contacts/model/Contact.java index 42b6914bf..33c6e02f1 100644 --- a/examples/contacts-example/src/main/java/de/saxsys/mvvmfx/examples/contacts/model/Contact.java +++ b/examples/contacts-example/src/main/java/de/saxsys/mvvmfx/examples/contacts/model/Contact.java @@ -2,102 +2,100 @@ import java.time.LocalDate; - /** * An entity class that represents a contact. */ public class Contact extends Identity { - + private String firstname; private String lastname; private String title; - + private LocalDate birthday; - + private String role; private String department; - + private String emailAddress; private String phoneNumber; private String mobileNumber; - + private final Address address = new Address(); - + public Address getAddress() { return address; } - + public String getFirstname() { return firstname; } - + public void setFirstname(String firstname) { this.firstname = firstname; } - + public String getLastname() { return lastname; } - + public void setLastname(String lastname) { this.lastname = lastname; } - + public String getTitle() { return title; } - + public void setTitle(String title) { this.title = title; } - + public LocalDate getBirthday() { return birthday; } - + public void setBirthday(LocalDate birthday) { this.birthday = birthday; } - + public String getRole() { return role; } - + public void setRole(String role) { this.role = role; } - + public String getDepartment() { return department; } - + public void setDepartment(String department) { this.department = department; } - + public String getEmailAddress() { return emailAddress; } - + public void setEmailAddress(String emailAddress) { this.emailAddress = emailAddress; } - + public String getPhoneNumber() { return phoneNumber; } - + public void setPhoneNumber(String phoneNumber) { this.phoneNumber = phoneNumber; } - + public String getMobileNumber() { return mobileNumber; } - + public void setMobileNumber(String mobileNumber) { this.mobileNumber = mobileNumber; } - - + } diff --git a/examples/contacts-example/src/main/java/de/saxsys/mvvmfx/examples/contacts/model/ContactFactory.java b/examples/contacts-example/src/main/java/de/saxsys/mvvmfx/examples/contacts/model/ContactFactory.java index d890e73e2..5a37c9d3b 100644 --- a/examples/contacts-example/src/main/java/de/saxsys/mvvmfx/examples/contacts/model/ContactFactory.java +++ b/examples/contacts-example/src/main/java/de/saxsys/mvvmfx/examples/contacts/model/ContactFactory.java @@ -5,90 +5,90 @@ import java.util.List; import java.util.Random; - /** - * A factory that produces contacts. This is used to create dummy data for tests and for the demo. + * A factory that produces contacts. This is used to create dummy data for tests + * and for the demo. */ public class ContactFactory { - + /** * @return a contact with random generated demo content. */ public static Contact createRandomContact() { Contact contact = new Contact(); - + contact.setFirstname(new Random().nextBoolean() ? getFemaleFirstname() : getMaleFirstname()); contact.setLastname(getLastname()); - + contact.setBirthday(getBirthday()); - + contact.setEmailAddress(getEmailAddress(contact.getFirstname())); contact.setPhoneNumber(getPhoneNumber()); contact.setMobileNumber(getPhoneNumber()); - + return contact; } - + private static LocalDate getBirthday() { int year = (2014 - 50) + new Random().nextInt(50); - + int month = new Random().nextInt(12) + 1; - + int day = new Random().nextInt(27) + 1; - + return LocalDate.of(year, month, day); } - + private static String getPhoneNumber() { StringBuilder number = new StringBuilder(); - + number.append("+49 "); - + for (int i = 0; i < 11; i++) { number.append(getRandomNumber()); } - + return number.toString(); } - + private static String getEmailAddress(String firstname) { StringBuilder emailAddress = new StringBuilder(); - + emailAddress.append(firstname); emailAddress.append(getRandomNumber()); emailAddress.append(getRandomNumber()); emailAddress.append("@"); - + List domains = Arrays.asList("example.com", "example.org", "mail.example.com"); emailAddress.append(domains.get(new Random().nextInt(domains.size()))); - + return emailAddress.toString(); } - + private static String getLastname() { - + List names = Arrays.asList("Smith", "Brown", "Lee", "Johnson", "Williams", "Müller", "Schmidt", "Schneider"); - + return names.get(new Random().nextInt(names.size())); } - + private static String getMaleFirstname() { - + List names = Arrays.asList("Max", "Paul", "Leon", "Lucas", "Jonas", "Ben", "Tim", "David"); - + return names.get(new Random().nextInt(names.size())); } - + private static String getFemaleFirstname() { - + List names = Arrays.asList("Marie", "Julia", "Anne", "Laura", "Lisa", "Sarah", "Michelle", "Sophie"); - + return names.get(new Random().nextInt(names.size())); } - + private static int getRandomNumber() { return new Random().nextInt(10); } - + } diff --git a/examples/contacts-example/src/main/java/de/saxsys/mvvmfx/examples/contacts/model/Country.java b/examples/contacts-example/src/main/java/de/saxsys/mvvmfx/examples/contacts/model/Country.java index 5d2ff144b..552a04350 100644 --- a/examples/contacts-example/src/main/java/de/saxsys/mvvmfx/examples/contacts/model/Country.java +++ b/examples/contacts-example/src/main/java/de/saxsys/mvvmfx/examples/contacts/model/Country.java @@ -1,6 +1,5 @@ package de.saxsys.mvvmfx.examples.contacts.model; - import javax.xml.bind.annotation.XmlAccessType; import javax.xml.bind.annotation.XmlAccessorType; import javax.xml.bind.annotation.XmlAttribute; @@ -9,42 +8,38 @@ @XmlRootElement(name = "iso_3166_entry") @XmlAccessorType(XmlAccessType.FIELD) public class Country { - + @XmlAttribute(name = "name") private String name; - + @XmlAttribute(name = "alpha_2_code") private String countryCode; - + Country() { - + } - + public Country(String name, String countryCode) { this.name = name; this.countryCode = countryCode; } - + public String getName() { return name; } - + public void setName(String name) { this.name = name; } - + public String getCountryCode() { return countryCode; } - - - + public void setCountryCode(String countryCode) { this.countryCode = countryCode; } - - - + @Override public boolean equals(Object o) { if (this == o) { @@ -53,33 +48,31 @@ public boolean equals(Object o) { if (o == null || getClass() != o.getClass()) { return false; } - - + if (name == null || countryCode == null) { return false; } - + Country country = (Country) o; - + if (!name.equals(country.name)) { return false; } - + if (!countryCode.equals(country.countryCode)) { return false; } - + return true; } - + @Override public int hashCode() { int result = name == null ? 13 : name.hashCode(); result += countryCode == null ? 13 : countryCode.hashCode(); return result; } - - + @Override public String toString() { return "Country:" + name + ", code:" + countryCode; 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 26093c920..8e5a9ab5c 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 @@ -27,55 +27,57 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; - /** - * This class is used to encapsulate the process of loading available countries and there subdivisions (if available). - * - * This class is meant to be a stateful wrapper around the existing countries. You should create an instance of this - * class, call the {@link #init()} method and then bind the UI to the provided observable lists ( + * This class is used to encapsulate the process of loading available countries + * and there subdivisions (if available). + * + * This class is meant to be a stateful wrapper around the existing countries. + * You should create an instance of this class, call the {@link #init()} method + * and then bind the UI to the provided observable lists ( * {@link #availableCountries()} and {@link #subdivisions()}). - * - * To choose a country have to use the {@link #setCountry(Country)} method. This will lead to a change of the - * {@link #subdivisions()} list. - * - * - * At the moment this class used two XML files ({@link #ISO_3166_LOCATION} and {@link #ISO_3166_2_LOCATION}) that - * contain information about countries, country-codes and subdivisions according to ISO 3166 and ISO 3166-2. - * + * + * To choose a country have to use the {@link #setCountry(Country)} method. This + * will lead to a change of the {@link #subdivisions()} list. + * + * + * At the moment this class used two XML files ({@link #ISO_3166_LOCATION} and + * {@link #ISO_3166_2_LOCATION}) that contain information about countries, + * country-codes and subdivisions according to ISO 3166 and ISO 3166-2. + * * The loading process is implemented with the DataFX framework. */ public class CountrySelector { - + private static final Logger LOG = LoggerFactory.getLogger(CountrySelector.class); - + public static final String ISO_3166_LOCATION = "/countries/iso_3166.xml"; public static final String ISO_3166_2_LOCATION = "/countries/iso_3166_2.xml"; private ObservableList countries = FXCollections.observableArrayList(); private ObservableList subdivisions = FXCollections.observableArrayList(); - - + private ReadOnlyStringWrapper subdivisionLabel = new ReadOnlyStringWrapper(); - + private ReadOnlyBooleanWrapper inProgress = new ReadOnlyBooleanWrapper(false); - + private Map> countryCodeSubdivisionMap = new HashMap<>(); private Map countryCodeSubdivisionNameMap = new HashMap<>(); - - + /** - * This method triggers the loading of the available countries and subdivisions. + * This method triggers the loading of the available countries and + * subdivisions. */ public void init() { inProgress.set(true); loadCountries(); } - + /** - * Set the currently selected country. This will lead to an update of the {@link #subdivisions()} observable list - * and the {@link #subdivisionLabel()}. - * - * @param country - * the country that will be selected or null if no country is selected. + * Set the currently selected country. This will lead to an update of the + * {@link #subdivisions()} observable list and the + * {@link #subdivisionLabel()}. + * + * @param country the country that will be selected or null if + * no country is selected. */ public void setCountry(Country country) { if (country == null) { @@ -83,16 +85,15 @@ public void setCountry(Country country) { subdivisions.clear(); return; } - + subdivisionLabel.set(countryCodeSubdivisionNameMap.get(country)); - + subdivisions.clear(); if (countryCodeSubdivisionMap.containsKey(country)) { subdivisions.addAll(countryCodeSubdivisionMap.get(country)); } } - - + /** * Load all countries from the XML file source with DataFX. */ @@ -102,15 +103,15 @@ void loadCountries() { throw new IllegalStateException("Can't find the list of countries! Expected location was:" + ISO_3166_LOCATION); } - + XmlConverter countryConverter = new XmlConverter<>("iso_3166_entry", Country.class); - + try { FileSource dataSource = new FileSource<>(new File(iso3166Resource.getFile()), countryConverter); ListDataProvider listDataProvider = new ListDataProvider<>(dataSource); - + listDataProvider.setResultObservableList(countries); - + Worker> worker = listDataProvider.retrieve(); // when the countries are loaded we start the loading of the subdivisions. worker.stateProperty().addListener(obs -> { @@ -122,124 +123,128 @@ void loadCountries() { LOG.error("A problem was detected while loading the XML file with the available countries.", e); } } - + /** * Load all subdivisions from the XML file source with DataFX. */ void loadSubdivisions() { - + URL iso3166_2Resource = this.getClass().getResource(ISO_3166_2_LOCATION); - + if (iso3166_2Resource == null) { - throw new IllegalStateException("Can't find the list of subdivisions! Expected location was:" + - ISO_3166_2_LOCATION); + throw new IllegalStateException("Can't find the list of subdivisions! Expected location was:" + + ISO_3166_2_LOCATION); } - + XmlConverter converter = new XmlConverter<>("iso_3166_country", ISO3166_2_CountryEntity.class); - + ObservableList subdivisionsEntities = FXCollections.observableArrayList(); - + try { FileSource dataSource = new FileSource<>(new File(iso3166_2Resource.getFile()), converter); ListDataProvider listDataProvider = new ListDataProvider<>(dataSource); - + listDataProvider.setResultObservableList(subdivisionsEntities); - + Worker> worker = listDataProvider.retrieve(); worker.stateProperty().addListener(obs -> { if (worker.getState() == Worker.State.SUCCEEDED) { - + subdivisionsEntities.forEach(entity -> { if (entity.subsets != null && !entity.subsets.isEmpty()) { - + Country country = findCountryByCode(entity.code); - + if (!countryCodeSubdivisionMap.containsKey(country)) { countryCodeSubdivisionMap.put(country, new ArrayList<>()); } - + List subdivisionList = countryCodeSubdivisionMap.get(country); - + entity.subsets.get(0).entryList.forEach(entry -> { subdivisionList.add(new Subdivision(entry.name, entry.code, country)); }); - + countryCodeSubdivisionNameMap.put(country, entity.subsets.get(0).subdivisionType); } }); - + inProgress.set(false); } }); } catch (IOException e) { LOG.error("A problem was detected while loading the XML file with the available subdivisions.", e); } - + } - + private Country findCountryByCode(String code) { return countries.stream().filter(country -> country.getCountryCode().equals(code)).findFirst().orElse(null); } - - - + /** - * XML entity class. These classes represent the structure of the XML files to be loaded. + * XML entity class. These classes represent the structure of the XML files + * to be loaded. */ @XmlRootElement(name = "iso_3166_subset") @XmlAccessorType(XmlAccessType.FIELD) static class ISO3166_2_EntryEntity { + @XmlAttribute(name = "code") public String code; @XmlAttribute(name = "name") public String name; } - + /** - * XML entity class. These classes represent the structure of the XML files to be loaded. + * XML entity class. These classes represent the structure of the XML files + * to be loaded. */ @XmlRootElement(name = "iso_3166_subset") @XmlAccessorType(XmlAccessType.FIELD) static class ISO3166_2_SubsetEntity { + @XmlElement(name = "iso_3166_2_entry") public List entryList; - + @XmlAttribute(name = "type") public String subdivisionType; } - + /** - * XML entity class. These classes represent the structure of the XML files to be loaded. + * XML entity class. These classes represent the structure of the XML files + * to be loaded. */ @XmlRootElement(name = "iso_3166_country") @XmlAccessorType(XmlAccessType.FIELD) static class ISO3166_2_CountryEntity { + @XmlAttribute(name = "code") public String code; - + @XmlElement(name = "iso_3166_subset") public List subsets; - + @Override public String toString() { return "CountryEntity " + code; } } - + public ObservableList availableCountries() { return countries; } - + public ReadOnlyStringProperty subdivisionLabel() { return subdivisionLabel.getReadOnlyProperty(); } - + public ObservableList subdivisions() { return FXCollections.unmodifiableObservableList(subdivisions); } - + public ReadOnlyBooleanProperty inProgressProperty() { return inProgress.getReadOnlyProperty(); } diff --git a/examples/contacts-example/src/main/java/de/saxsys/mvvmfx/examples/contacts/model/Identity.java b/examples/contacts-example/src/main/java/de/saxsys/mvvmfx/examples/contacts/model/Identity.java index a85b6a2c6..d94e8bf4b 100644 --- a/examples/contacts-example/src/main/java/de/saxsys/mvvmfx/examples/contacts/model/Identity.java +++ b/examples/contacts-example/src/main/java/de/saxsys/mvvmfx/examples/contacts/model/Identity.java @@ -2,24 +2,24 @@ import java.util.UUID; - /** - * This is a base class for model entities that represent identities. This means that they are distinguished by there - * identity (id) and not by there values. Two instances can be considered "the same" when they have the same ID, + * This is a base class for model entities that represent identities. This means + * that they are distinguished by there identity (id) and not by there values. + * Two instances can be considered "the same" when they have the same ID, * independently what values they have. */ public abstract class Identity { - + private String id; - + public Identity() { id = UUID.randomUUID().toString(); } - + public String getId() { return id; } - + @Override public boolean equals(Object o) { if (this == o) { @@ -28,18 +28,19 @@ public boolean equals(Object o) { if (o == null || getClass() != o.getClass()) { return false; } - + Identity identity = (Identity) o; - + if (!id.equals(identity.id)) { return false; } - + return true; } - + @Override public int hashCode() { return id.hashCode(); } + } diff --git a/examples/contacts-example/src/main/java/de/saxsys/mvvmfx/examples/contacts/model/InmemoryRepository.java b/examples/contacts-example/src/main/java/de/saxsys/mvvmfx/examples/contacts/model/InmemoryRepository.java index 2ebdbb20f..f0513b2ab 100644 --- a/examples/contacts-example/src/main/java/de/saxsys/mvvmfx/examples/contacts/model/InmemoryRepository.java +++ b/examples/contacts-example/src/main/java/de/saxsys/mvvmfx/examples/contacts/model/InmemoryRepository.java @@ -12,38 +12,38 @@ @Singleton public class InmemoryRepository implements Repository { - - private Set contacts = new HashSet<>(); - + + private final Set contacts = new HashSet<>(); + @Inject private Event contactsUpdatedEvent; - - + @Override public Set findAll() { return Collections.unmodifiableSet(contacts); } - + @Override public Optional findById(String id) { return contacts.stream().filter(contact -> contact.getId().equals(id)).findFirst(); } - + @Override public void save(Contact contact) { contacts.add(contact); fireUpdateEvent(); } - + @Override public void delete(Contact contact) { contacts.remove(contact); fireUpdateEvent(); } - + private void fireUpdateEvent() { if (contactsUpdatedEvent != null) { contactsUpdatedEvent.fire(new ContactsUpdatedEvent()); } } + } diff --git a/examples/contacts-example/src/main/java/de/saxsys/mvvmfx/examples/contacts/model/Repository.java b/examples/contacts-example/src/main/java/de/saxsys/mvvmfx/examples/contacts/model/Repository.java index 7bab2c4d5..c2a51df0e 100644 --- a/examples/contacts-example/src/main/java/de/saxsys/mvvmfx/examples/contacts/model/Repository.java +++ b/examples/contacts-example/src/main/java/de/saxsys/mvvmfx/examples/contacts/model/Repository.java @@ -4,13 +4,13 @@ import java.util.Set; public interface Repository { - - + Set findAll(); - + Optional findById(String id); - + void save(Contact contact); - + void delete(Contact contact); + } diff --git a/examples/contacts-example/src/main/java/de/saxsys/mvvmfx/examples/contacts/model/Subdivision.java b/examples/contacts-example/src/main/java/de/saxsys/mvvmfx/examples/contacts/model/Subdivision.java index 4c2e1c96b..33765142f 100644 --- a/examples/contacts-example/src/main/java/de/saxsys/mvvmfx/examples/contacts/model/Subdivision.java +++ b/examples/contacts-example/src/main/java/de/saxsys/mvvmfx/examples/contacts/model/Subdivision.java @@ -1,30 +1,30 @@ package de.saxsys.mvvmfx.examples.contacts.model; public class Subdivision { - + private final String name; private final String abbr; - + private final Country country; - + public Subdivision(String name, String abbr, Country country) { this.name = name; this.abbr = abbr; this.country = country; } - + public String getName() { return name; } - + public String getAbbr() { return abbr; } - + public Country getCountry() { return country; } - + @Override public boolean equals(Object o) { if (this == o) { @@ -33,9 +33,9 @@ public boolean equals(Object o) { if (o == null || getClass() != o.getClass()) { return false; } - + Subdivision that = (Subdivision) o; - + if (!abbr.equals(that.abbr)) { return false; } @@ -45,10 +45,10 @@ public boolean equals(Object o) { if (!name.equals(that.name)) { return false; } - + return true; } - + @Override public int hashCode() { int result = name.hashCode(); @@ -56,4 +56,5 @@ public int hashCode() { result = 31 * result + country.hashCode(); return result; } + } diff --git a/examples/contacts-example/src/main/resources/de/saxsys/mvvmfx/examples/contacts/ui/about/AboutAuthorView.fxml b/examples/contacts-example/src/main/java/de/saxsys/mvvmfx/examples/contacts/ui/about/AboutAuthorView.fxml similarity index 94% rename from examples/contacts-example/src/main/resources/de/saxsys/mvvmfx/examples/contacts/ui/about/AboutAuthorView.fxml rename to examples/contacts-example/src/main/java/de/saxsys/mvvmfx/examples/contacts/ui/about/AboutAuthorView.fxml index 84b26dac3..0acf58880 100644 --- a/examples/contacts-example/src/main/resources/de/saxsys/mvvmfx/examples/contacts/ui/about/AboutAuthorView.fxml +++ b/examples/contacts-example/src/main/java/de/saxsys/mvvmfx/examples/contacts/ui/about/AboutAuthorView.fxml @@ -8,7 +8,7 @@ + xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1" fx:controller="de.saxsys.mvvmfx.examples.contacts.ui.about.AboutAuthorView"> diff --git a/examples/contacts-example/src/main/java/de/saxsys/mvvmfx/examples/contacts/ui/about/AboutAuthorView.java b/examples/contacts-example/src/main/java/de/saxsys/mvvmfx/examples/contacts/ui/about/AboutAuthorView.java index 4f02e51ac..2ae07c613 100644 --- a/examples/contacts-example/src/main/java/de/saxsys/mvvmfx/examples/contacts/ui/about/AboutAuthorView.java +++ b/examples/contacts-example/src/main/java/de/saxsys/mvvmfx/examples/contacts/ui/about/AboutAuthorView.java @@ -1,46 +1,22 @@ package de.saxsys.mvvmfx.examples.contacts.ui.about; -import de.saxsys.mvvmfx.FluentViewLoader; +import javax.inject.Singleton; + import de.saxsys.mvvmfx.FxmlView; import de.saxsys.mvvmfx.InjectViewModel; -import de.saxsys.mvvmfx.examples.contacts.events.OpenAuthorPageEvent; -import de.saxsys.mvvmfx.examples.contacts.util.DialogHelper; import javafx.fxml.FXML; -import javafx.scene.Parent; -import javafx.stage.Stage; - -import javax.enterprise.event.Observes; -import javax.inject.Inject; +@Singleton public class AboutAuthorView implements FxmlView { - - - private Parent root; - - @Inject - private Stage primaryStage; - - + @InjectViewModel private AboutAuthorViewModel viewModel; - - AboutAuthorView() { - root = FluentViewLoader.fxmlView(AboutAuthorView.class).codeBehind(this).load().getView(); - } - - public void initialize() { - DialogHelper.initDialog(viewModel.dialogOpenProperty(), primaryStage, () -> root); - } - - public void openDialog(@Observes OpenAuthorPageEvent event) { - viewModel.openDialog(); - } - + @FXML public void openBlog() { viewModel.openBlog(); } - + @FXML public void openTwitter() { viewModel.openTwitter(); diff --git a/examples/contacts-example/src/main/java/de/saxsys/mvvmfx/examples/contacts/ui/about/AboutAuthorViewModel.java b/examples/contacts-example/src/main/java/de/saxsys/mvvmfx/examples/contacts/ui/about/AboutAuthorViewModel.java index 5140d289e..ce35bd5b7 100644 --- a/examples/contacts-example/src/main/java/de/saxsys/mvvmfx/examples/contacts/ui/about/AboutAuthorViewModel.java +++ b/examples/contacts-example/src/main/java/de/saxsys/mvvmfx/examples/contacts/ui/about/AboutAuthorViewModel.java @@ -1,32 +1,21 @@ package de.saxsys.mvvmfx.examples.contacts.ui.about; +import javax.inject.Inject; + import de.saxsys.mvvmfx.ViewModel; import javafx.application.HostServices; -import javafx.beans.property.BooleanProperty; -import javafx.beans.property.SimpleBooleanProperty; - -import javax.inject.Inject; public class AboutAuthorViewModel implements ViewModel { - - private BooleanProperty dialogOpen = new SimpleBooleanProperty(); - + @Inject private HostServices hostServices; - - public BooleanProperty dialogOpenProperty() { - return dialogOpen; - } - + public void openBlog() { hostServices.showDocument("http://www.lestard.eu"); } - + public void openTwitter() { hostServices.showDocument("https://twitter.com/manuel_mauky"); } - - public void openDialog() { - dialogOpen.set(true); - } + } diff --git a/examples/contacts-example/src/main/resources/de/saxsys/mvvmfx/examples/contacts/ui/about/AboutView.fxml b/examples/contacts-example/src/main/java/de/saxsys/mvvmfx/examples/contacts/ui/about/AboutView.fxml similarity index 61% rename from examples/contacts-example/src/main/resources/de/saxsys/mvvmfx/examples/contacts/ui/about/AboutView.fxml rename to examples/contacts-example/src/main/java/de/saxsys/mvvmfx/examples/contacts/ui/about/AboutView.fxml index b7a8f48e6..be8bca1f9 100644 --- a/examples/contacts-example/src/main/resources/de/saxsys/mvvmfx/examples/contacts/ui/about/AboutView.fxml +++ b/examples/contacts-example/src/main/java/de/saxsys/mvvmfx/examples/contacts/ui/about/AboutView.fxml @@ -8,40 +8,37 @@ - + - + - + - + - + - - + - + - + diff --git a/examples/contacts-example/src/main/java/de/saxsys/mvvmfx/examples/contacts/ui/about/AboutView.java b/examples/contacts-example/src/main/java/de/saxsys/mvvmfx/examples/contacts/ui/about/AboutView.java index fa1d73c3f..f80196417 100644 --- a/examples/contacts-example/src/main/java/de/saxsys/mvvmfx/examples/contacts/ui/about/AboutView.java +++ b/examples/contacts-example/src/main/java/de/saxsys/mvvmfx/examples/contacts/ui/about/AboutView.java @@ -1,11 +1,5 @@ package de.saxsys.mvvmfx.examples.contacts.ui.about; -import javafx.fxml.FXML; -import javafx.scene.Parent; -import javafx.scene.control.Hyperlink; -import javafx.stage.Stage; - -import javax.enterprise.event.Observes; import javax.inject.Inject; import javax.inject.Singleton; @@ -14,33 +8,25 @@ import de.saxsys.mvvmfx.FluentViewLoader; import de.saxsys.mvvmfx.FxmlView; import de.saxsys.mvvmfx.InjectViewModel; -import de.saxsys.mvvmfx.examples.contacts.events.OpenAboutDialogEvent; import de.saxsys.mvvmfx.examples.contacts.util.DialogHelper; +import javafx.fxml.FXML; +import javafx.scene.Parent; +import javafx.scene.control.Hyperlink; +import javafx.stage.Stage; @Singleton public class AboutView implements FxmlView { - - @Inject - private Stage primaryStage; - + @FXML private HyperlinkLabel librariesLabel; - + @InjectViewModel private AboutViewModel viewModel; - - private Parent root; - + @Inject - AboutView() { - root = FluentViewLoader.fxmlView(AboutView.class) - .codeBehind(this) - .load().getView(); - } - + private Stage primaryStage; + public void initialize() { - DialogHelper.initDialog(viewModel.dialogOpenProperty(), primaryStage, () -> root); - librariesLabel.textProperty().bind(viewModel.librariesLabelTextProperty()); librariesLabel.setOnAction(event -> { Hyperlink link = (Hyperlink) event.getSource(); @@ -48,13 +34,12 @@ public void initialize() { viewModel.onLinkClicked(str); }); } - + @FXML public void openAuthorPage() { - viewModel.openAuthorPage(); - } - - public void open(@Observes OpenAboutDialogEvent event) { - viewModel.openDialog(); + Parent view = FluentViewLoader.fxmlView(AboutAuthorView.class) + .load().getView(); + DialogHelper.showDialog(view, primaryStage, "/contacts.css"); } + } diff --git a/examples/contacts-example/src/main/java/de/saxsys/mvvmfx/examples/contacts/ui/about/AboutViewModel.java b/examples/contacts-example/src/main/java/de/saxsys/mvvmfx/examples/contacts/ui/about/AboutViewModel.java index 22e24f4e7..03b23c0a0 100644 --- a/examples/contacts-example/src/main/java/de/saxsys/mvvmfx/examples/contacts/ui/about/AboutViewModel.java +++ b/examples/contacts-example/src/main/java/de/saxsys/mvvmfx/examples/contacts/ui/about/AboutViewModel.java @@ -2,60 +2,57 @@ import java.util.function.Consumer; +import javax.annotation.PostConstruct; +import javax.inject.Inject; + +import de.saxsys.mvvmfx.ViewModel; +import de.saxsys.mvvmfx.utils.notifications.NotificationCenter; import javafx.application.HostServices; -import javafx.beans.property.BooleanProperty; import javafx.beans.property.ReadOnlyStringProperty; import javafx.beans.property.ReadOnlyStringWrapper; -import javafx.beans.property.SimpleBooleanProperty; import javafx.collections.FXCollections; import javafx.collections.MapChangeListener; import javafx.collections.ObservableMap; -import javax.annotation.PostConstruct; -import javax.enterprise.event.Event; -import javax.inject.Inject; - -import de.saxsys.mvvmfx.ViewModel; -import de.saxsys.mvvmfx.examples.contacts.events.OpenAuthorPageEvent; - public class AboutViewModel implements ViewModel { - - private BooleanProperty dialogOpen = new SimpleBooleanProperty(); - - private ReadOnlyStringWrapper librariesLabelText = new ReadOnlyStringWrapper(""); - - ObservableMap libraryLinkMap = FXCollections.observableHashMap(); - - @Inject - private Event openAuthorPageEvent; - + @Inject private HostServices hostServices; - + + @Inject + private NotificationCenter notificationCenter; + + private final ReadOnlyStringWrapper librariesLabelText = new ReadOnlyStringWrapper(""); + + // Package Private because of testing reasons + ObservableMap libraryLinkMap = FXCollections.observableHashMap(); + /** - * Sadly the {@link javafx.application.HostServices} class of JavaFX is final so we can't mock it in - * tests. To still be able to test link actions we have introduced this handler as a mockable indirection. + * Sadly the {@link javafx.application.HostServices} class of JavaFX is + * final so we can't mock it in tests. To still be able to test + * link actions we have introduced this handler as a mockable indirection. */ Consumer onLinkClickedHandler; - + public AboutViewModel() { + libraryLinkMap.addListener((MapChangeListener) change -> { StringBuilder labelText = new StringBuilder(); - + libraryLinkMap.keySet().stream().sorted().forEach(libraryName -> { labelText.append("- ["); labelText.append(libraryName); labelText.append("]\n"); }); - + librariesLabelText.set(labelText.toString()); }); } - + @PostConstruct public void initLibraryMap() { onLinkClickedHandler = hostServices::showDocument; - + libraryLinkMap.put("DataFX", "http://www.javafxdata.org/"); libraryLinkMap.put("ControlsFX", "http://fxexperience.com/controlsfx/"); libraryLinkMap.put("FontAwesomeFX", "https://bitbucket.org/Jerady/fontawesomefx"); @@ -63,26 +60,15 @@ public void initLibraryMap() { libraryLinkMap.put("AssertJ-JavaFX", "https://github.com/lestard/assertj-javafx"); libraryLinkMap.put("JFX-Testrunner", "https://github.com/sialcasa/jfx-testrunner"); } - - public void openDialog() { - dialogOpen.set(true); - } - + public void onLinkClicked(String linkText) { if (libraryLinkMap.containsKey(linkText)) { onLinkClickedHandler.accept(libraryLinkMap.get(linkText)); } } - - public BooleanProperty dialogOpenProperty() { - return dialogOpen; - } - + public ReadOnlyStringProperty librariesLabelTextProperty() { return librariesLabelText.getReadOnlyProperty(); } - - public void openAuthorPage() { - openAuthorPageEvent.fire(new OpenAuthorPageEvent()); - } + } diff --git a/examples/contacts-example/src/main/resources/de/saxsys/mvvmfx/examples/contacts/ui/about/profile_manuel.png b/examples/contacts-example/src/main/java/de/saxsys/mvvmfx/examples/contacts/ui/about/profile_manuel.png similarity index 100% rename from examples/contacts-example/src/main/resources/de/saxsys/mvvmfx/examples/contacts/ui/about/profile_manuel.png rename to examples/contacts-example/src/main/java/de/saxsys/mvvmfx/examples/contacts/ui/about/profile_manuel.png diff --git a/examples/contacts-example/src/main/java/de/saxsys/mvvmfx/examples/contacts/ui/addcontact/AddContactDialog.java b/examples/contacts-example/src/main/java/de/saxsys/mvvmfx/examples/contacts/ui/addcontact/AddContactDialog.java deleted file mode 100644 index 70fc4fcd4..000000000 --- a/examples/contacts-example/src/main/java/de/saxsys/mvvmfx/examples/contacts/ui/addcontact/AddContactDialog.java +++ /dev/null @@ -1,52 +0,0 @@ -package de.saxsys.mvvmfx.examples.contacts.ui.addcontact; - -import javafx.fxml.FXML; -import javafx.scene.Parent; -import javafx.stage.Stage; - -import javax.enterprise.event.Observes; -import javax.inject.Inject; -import javax.inject.Singleton; - -import de.saxsys.mvvmfx.FluentViewLoader; -import de.saxsys.mvvmfx.FxmlView; -import de.saxsys.mvvmfx.InjectViewModel; -import de.saxsys.mvvmfx.examples.contacts.events.OpenAddContactDialogEvent; -import de.saxsys.mvvmfx.examples.contacts.ui.contactdialog.ContactDialogView; -import de.saxsys.mvvmfx.examples.contacts.util.DialogHelper; - -@Singleton -public class AddContactDialog implements FxmlView { - - - @FXML - private ContactDialogView contactDialogViewController; - - - @Inject - private Stage primaryStage; - - @InjectViewModel - private AddContactDialogViewModel viewModel; - - private Parent root; - - @Inject - AddContactDialog() { - root = FluentViewLoader - .fxmlView(AddContactDialog.class) - .codeBehind(this) - .load() - .getView(); - } - - public void initialize() { - viewModel.setContactDialogViewModel(contactDialogViewController.getViewModel()); - - DialogHelper.initDialog(viewModel.dialogOpenProperty(), primaryStage, () -> root); - } - - public void open(@Observes OpenAddContactDialogEvent event) { - viewModel.openDialog(); - } -} diff --git a/examples/contacts-example/src/main/resources/de/saxsys/mvvmfx/examples/contacts/ui/editcontact/EditContactDialog.fxml b/examples/contacts-example/src/main/java/de/saxsys/mvvmfx/examples/contacts/ui/addcontact/AddContactDialogView.fxml similarity index 79% rename from examples/contacts-example/src/main/resources/de/saxsys/mvvmfx/examples/contacts/ui/editcontact/EditContactDialog.fxml rename to examples/contacts-example/src/main/java/de/saxsys/mvvmfx/examples/contacts/ui/addcontact/AddContactDialogView.fxml index bcebd0bc5..c29c29710 100644 --- a/examples/contacts-example/src/main/resources/de/saxsys/mvvmfx/examples/contacts/ui/editcontact/EditContactDialog.fxml +++ b/examples/contacts-example/src/main/java/de/saxsys/mvvmfx/examples/contacts/ui/addcontact/AddContactDialogView.fxml @@ -1,7 +1,7 @@ - + { + + @InjectViewModel + private AddContactDialogViewModel viewModel; + + private Stage showDialog; + + public void initialize() { + viewModel.subscribe(AddContactDialogViewModel.CLOSE_DIALOG_NOTIFICATION, (key, payload) -> { + showDialog.close(); + }); + } + + public void setDisplayingStage(Stage showDialog) { + this.showDialog = showDialog; + } + +} diff --git a/examples/contacts-example/src/main/java/de/saxsys/mvvmfx/examples/contacts/ui/addcontact/AddContactDialogViewModel.java b/examples/contacts-example/src/main/java/de/saxsys/mvvmfx/examples/contacts/ui/addcontact/AddContactDialogViewModel.java index 0f3438e64..ffd2ed1ab 100644 --- a/examples/contacts-example/src/main/java/de/saxsys/mvvmfx/examples/contacts/ui/addcontact/AddContactDialogViewModel.java +++ b/examples/contacts-example/src/main/java/de/saxsys/mvvmfx/examples/contacts/ui/addcontact/AddContactDialogViewModel.java @@ -1,70 +1,55 @@ package de.saxsys.mvvmfx.examples.contacts.ui.addcontact; import java.util.ResourceBundle; -import javafx.beans.property.BooleanProperty; -import javafx.beans.property.SimpleBooleanProperty; import javax.inject.Inject; +import de.saxsys.mvvmfx.InjectScope; import de.saxsys.mvvmfx.ViewModel; import de.saxsys.mvvmfx.examples.contacts.model.Contact; import de.saxsys.mvvmfx.examples.contacts.model.Repository; -import de.saxsys.mvvmfx.examples.contacts.ui.contactdialog.ContactDialogViewModel; +import de.saxsys.mvvmfx.examples.contacts.ui.scopes.ContactDialogScope; public class AddContactDialogViewModel implements ViewModel { + + public static final String CLOSE_DIALOG_NOTIFICATION = "closeDialog"; + static final String TITLE_LABEL_KEY = "dialog.addcontact.title"; - - private BooleanProperty dialogOpen = new SimpleBooleanProperty(); - + @Inject private Repository repository; - - private ContactDialogViewModel contactDialogViewModel; - + + @InjectScope + private ContactDialogScope dialogScope; + @Inject private ResourceBundle defaultResourceBundle; - - public AddContactDialogViewModel() { - dialogOpen.addListener((obs, oldV, newV) -> { - if (!newV) { - contactDialogViewModel.resetDialogPage(); - } + + public void initialize() { + dialogScope.subscribe(ContactDialogScope.OK_BEFORE_COMMIT, (key, payload) -> { + addContactAction(); }); + + dialogScope.dialogTitleProperty().set(defaultResourceBundle.getString(TITLE_LABEL_KEY)); + dialogScope.publish(ContactDialogScope.RESET_FORMS); + Contact contact = new Contact(); + dialogScope.setContactToEdit(contact); } - - - public void setContactDialogViewModel(ContactDialogViewModel contactDialogViewModel) { - this.contactDialogViewModel = contactDialogViewModel; - - contactDialogViewModel.setOkAction(this::addContactAction); - contactDialogViewModel.titleTextProperty().set(defaultResourceBundle.getString(TITLE_LABEL_KEY)); - } - - + public void addContactAction() { - if (contactDialogViewModel.validProperty().get()) { - - contactDialogViewModel.getAddressFormViewModel().commitChanges(); - Contact contact = contactDialogViewModel.getContactFormViewModel().getContact(); - + if (dialogScope.isContactFormValid()) { + + dialogScope.publish(ContactDialogScope.COMMIT); + + Contact contact = dialogScope.getContactToEdit(); + repository.save(contact); - - dialogOpen.set(false); + + dialogScope.publish(ContactDialogScope.RESET_DIALOG_PAGE); + dialogScope.setContactToEdit(null); + + publish(CLOSE_DIALOG_NOTIFICATION); } } - - public void openDialog() { - contactDialogViewModel.resetForms(); - - Contact contact = new Contact(); - contactDialogViewModel.getContactFormViewModel().initWithContact(contact); - contactDialogViewModel.getAddressFormViewModel().initWithAddress(contact.getAddress()); - - this.dialogOpenProperty().set(true); - } - - - public BooleanProperty dialogOpenProperty() { - return dialogOpen; - } + } diff --git a/examples/contacts-example/src/main/resources/de/saxsys/mvvmfx/examples/contacts/ui/addressform/AddressFormView.fxml b/examples/contacts-example/src/main/java/de/saxsys/mvvmfx/examples/contacts/ui/addressform/AddressFormView.fxml similarity index 100% rename from examples/contacts-example/src/main/resources/de/saxsys/mvvmfx/examples/contacts/ui/addressform/AddressFormView.fxml rename to examples/contacts-example/src/main/java/de/saxsys/mvvmfx/examples/contacts/ui/addressform/AddressFormView.fxml diff --git a/examples/contacts-example/src/main/java/de/saxsys/mvvmfx/examples/contacts/ui/addressform/AddressFormView.java b/examples/contacts-example/src/main/java/de/saxsys/mvvmfx/examples/contacts/ui/addressform/AddressFormView.java index 532c338cb..226fabd05 100644 --- a/examples/contacts-example/src/main/java/de/saxsys/mvvmfx/examples/contacts/ui/addressform/AddressFormView.java +++ b/examples/contacts-example/src/main/java/de/saxsys/mvvmfx/examples/contacts/ui/addressform/AddressFormView.java @@ -1,57 +1,56 @@ package de.saxsys.mvvmfx.examples.contacts.ui.addressform; +import de.saxsys.mvvmfx.FxmlView; +import de.saxsys.mvvmfx.InjectViewModel; import javafx.fxml.FXML; import javafx.scene.control.ComboBox; import javafx.scene.control.Label; import javafx.scene.control.ProgressIndicator; import javafx.scene.control.TextField; -import de.saxsys.mvvmfx.FxmlView; -import de.saxsys.mvvmfx.InjectViewModel; public class AddressFormView implements FxmlView { + @FXML public TextField streetInput; @FXML public TextField postalcodeInput; @FXML public TextField cityInput; + @FXML public ComboBox countryInput; @FXML public ComboBox federalStateInput; + @FXML public Label subdivisionLabel; - @FXML public Label countryLabel; - + @FXML public ProgressIndicator loadingIndicator; - - + @InjectViewModel private AddressFormViewModel viewModel; - - + public void initialize() { - subdivisionLabel.textProperty().bind(viewModel.subdivisionLabel()); - subdivisionLabel.disableProperty().bind(viewModel.subdivisionInputDisabledProperty()); - + loadingIndicator.visibleProperty().bind(viewModel.loadingInProgressProperty()); countryLabel.disableProperty().bind(viewModel.countryInputDisabledProperty()); - + streetInput.textProperty().bindBidirectional(viewModel.streetProperty()); postalcodeInput.textProperty().bindBidirectional(viewModel.postalCodeProperty()); cityInput.textProperty().bindBidirectional(viewModel.cityProperty()); - + countryInput.setItems(viewModel.countriesList()); countryInput.valueProperty().bindBidirectional(viewModel.selectedCountryProperty()); countryInput.disableProperty().bind(viewModel.countryInputDisabledProperty()); - + federalStateInput.setItems(viewModel.subdivisionsList()); federalStateInput.valueProperty().bindBidirectional(viewModel.selectedSubdivisionProperty()); federalStateInput.disableProperty().bind(viewModel.subdivisionInputDisabledProperty()); - - loadingIndicator.visibleProperty().bind(viewModel.loadingInProgressProperty()); - + + subdivisionLabel.textProperty().bind(viewModel.subdivisionLabel()); + subdivisionLabel.disableProperty().bind(viewModel.subdivisionInputDisabledProperty()); } + } diff --git a/examples/contacts-example/src/main/java/de/saxsys/mvvmfx/examples/contacts/ui/addressform/AddressFormViewModel.java b/examples/contacts-example/src/main/java/de/saxsys/mvvmfx/examples/contacts/ui/addressform/AddressFormViewModel.java index cf1af56fe..52a54151d 100644 --- a/examples/contacts-example/src/main/java/de/saxsys/mvvmfx/examples/contacts/ui/addressform/AddressFormViewModel.java +++ b/examples/contacts-example/src/main/java/de/saxsys/mvvmfx/examples/contacts/ui/addressform/AddressFormViewModel.java @@ -3,8 +3,19 @@ import java.util.Optional; import java.util.ResourceBundle; +import javax.inject.Inject; + +import de.saxsys.mvvmfx.InjectScope; +import de.saxsys.mvvmfx.ViewModel; import de.saxsys.mvvmfx.examples.contacts.model.Address; +import de.saxsys.mvvmfx.examples.contacts.model.Contact; +import de.saxsys.mvvmfx.examples.contacts.model.Country; +import de.saxsys.mvvmfx.examples.contacts.model.CountrySelector; +import de.saxsys.mvvmfx.examples.contacts.model.Subdivision; +import de.saxsys.mvvmfx.examples.contacts.ui.scopes.ContactDialogScope; +import de.saxsys.mvvmfx.utils.itemlist.ItemList; import javafx.beans.binding.Bindings; +import javafx.beans.binding.ObjectBinding; import javafx.beans.property.ObjectProperty; import javafx.beans.property.ReadOnlyBooleanProperty; import javafx.beans.property.ReadOnlyBooleanWrapper; @@ -17,67 +28,80 @@ import javafx.collections.ListChangeListener; import javafx.collections.ObservableList; -import javax.annotation.PostConstruct; -import javax.inject.Inject; - -import de.saxsys.mvvmfx.ViewModel; -import de.saxsys.mvvmfx.examples.contacts.model.Country; -import de.saxsys.mvvmfx.examples.contacts.model.CountrySelector; -import de.saxsys.mvvmfx.examples.contacts.model.Subdivision; -import de.saxsys.mvvmfx.utils.itemlist.ItemList; - public class AddressFormViewModel implements ViewModel { + static final String NOTHING_SELECTED_MARKER = "---"; static final String SUBDIVISION_LABEL_KEY = "addressform.subdivision.label"; - - private ReadOnlyBooleanWrapper valid = new ReadOnlyBooleanWrapper(true); + private ObservableList countries; private ObservableList subdivisions; - private ReadOnlyStringWrapper subdivisionLabel = new ReadOnlyStringWrapper(); - - private StringProperty street = new SimpleStringProperty(); - private StringProperty postalCode = new SimpleStringProperty(); - private StringProperty city = new SimpleStringProperty(); - private ObjectProperty subdivision = new SimpleObjectProperty<>(); - private ObjectProperty country = new SimpleObjectProperty<>(); - - private StringProperty selectedCountry = new SimpleStringProperty(NOTHING_SELECTED_MARKER); - private StringProperty selectedSubdivision = new SimpleStringProperty(NOTHING_SELECTED_MARKER); - - private ReadOnlyBooleanWrapper loadingInProgress = new ReadOnlyBooleanWrapper(); - private ReadOnlyBooleanWrapper countryInputDisabled = new ReadOnlyBooleanWrapper(); - private ReadOnlyBooleanWrapper subdivisionInputDisabled = new ReadOnlyBooleanWrapper(); - + + private final ReadOnlyBooleanWrapper valid = new ReadOnlyBooleanWrapper(true); + private final ReadOnlyStringWrapper subdivisionLabel = new ReadOnlyStringWrapper(); + + private final StringProperty street = new SimpleStringProperty(); + private final StringProperty postalCode = new SimpleStringProperty(); + private final StringProperty city = new SimpleStringProperty(); + private final ObjectProperty subdivision = new SimpleObjectProperty<>(); + private final ObjectProperty country = new SimpleObjectProperty<>(); + + private final StringProperty selectedCountry = new SimpleStringProperty(NOTHING_SELECTED_MARKER); + private final StringProperty selectedSubdivision = new SimpleStringProperty(NOTHING_SELECTED_MARKER); + + private final ReadOnlyBooleanWrapper loadingInProgress = new ReadOnlyBooleanWrapper(); + private final ReadOnlyBooleanWrapper countryInputDisabled = new ReadOnlyBooleanWrapper(); + private final ReadOnlyBooleanWrapper subdivisionInputDisabled = new ReadOnlyBooleanWrapper(); + @Inject CountrySelector countrySelector; - + @Inject ResourceBundle resourceBundle; - - - + + @InjectScope + ContactDialogScope dialogScope; + // Don't inline this field. It's needed to prevent the list mapping from being garbage collected. private ItemList countryItemList; // Don't inline this field. It's needed to prevent the list mapping from being garbage collected. private ItemList subdivisionItemList; private Address address; - - @PostConstruct - public void init() { - + + private ObjectBinding contactBinding; + + public void initialize() { + dialogScope.subscribe(ContactDialogScope.RESET_FORMS, (key, payload) -> resetForm()); + dialogScope.subscribe(ContactDialogScope.COMMIT, (key, payload) -> commitChanges()); + + ObjectProperty contactToEditProperty = dialogScope.contactToEditProperty(); + + if (contactToEditProperty.get() != null) { + initWithAddress(contactToEditProperty.get().getAddress()); + } + + contactToEditProperty.addListener((observable, oldValue, newValue) -> { + if (newValue != null) { + if (newValue.getAddress() == null) { + System.out.println("Address is null"); + } else { + initWithAddress(newValue.getAddress()); + } + } + }); + loadingInProgress.bind(countrySelector.inProgressProperty()); countrySelector.init(); - + initSubdivisionLabel(); initCountryList(); initSubdivisionList(); - + selectedCountry.addListener((obs, oldV, newV) -> { if (newV != null && !newV.equals(NOTHING_SELECTED_MARKER)) { Optional matchingCountry = countrySelector.availableCountries().stream() .filter(country -> newV.equals(country.getName())) .findFirst(); - + if (matchingCountry.isPresent()) { countrySelector.setCountry(matchingCountry.get()); country.set(matchingCountry.get()); @@ -88,12 +112,12 @@ public void init() { } selectedSubdivision.set(NOTHING_SELECTED_MARKER); }); - + selectedSubdivision.addListener((obs, oldV, newV) -> { if (newV != null && !newV.equals(NOTHING_SELECTED_MARKER)) { Optional subdivisionOptional = countrySelector.subdivisions().stream() .filter(subdivision -> subdivision.getName().equals(newV)).findFirst(); - + if (subdivisionOptional.isPresent()) { subdivision.set(subdivisionOptional.get()); } else { @@ -103,53 +127,49 @@ public void init() { subdivision.set(null); } }); - + countryInputDisabled.bind(loadingInProgress); subdivisionInputDisabled.bind(loadingInProgress.or(Bindings.size(subdivisionsList()).lessThanOrEqualTo(1))); + + dialogScope.addressFormValidProperty().bind(valid); } - + void initSubdivisionLabel() { subdivisionLabel.bind( Bindings.when( countrySelector.subdivisionLabel().isEmpty()) - .then(resourceBundle.getString(SUBDIVISION_LABEL_KEY)) - .otherwise(countrySelector.subdivisionLabel())); + .then(resourceBundle.getString(SUBDIVISION_LABEL_KEY)) + .otherwise(countrySelector.subdivisionLabel())); } - + private void initSubdivisionList() { subdivisionItemList = new ItemList<>(countrySelector.subdivisions(), Subdivision::getName); subdivisions = createListWithNothingSelectedMarker(subdivisionItemList.getTargetList()); subdivisions.addListener((ListChangeListener) c -> selectedSubdivision.set(NOTHING_SELECTED_MARKER)); } - + private void initCountryList() { countryItemList = new ItemList<>(countrySelector.availableCountries(), Country::getName); - ObservableList mappedList = countryItemList.getTargetList(); - - countries = createListWithNothingSelectedMarker( - mappedList); - + + countries = createListWithNothingSelectedMarker(mappedList); countries.addListener((ListChangeListener) c -> selectedCountry.set(NOTHING_SELECTED_MARKER)); } - - - public void commitChanges() { + + private void commitChanges() { address.setStreet(street.get()); address.setCity(city.get()); address.setPostalcode(postalCode.get()); - address.setCountry(country.get()); address.setSubdivision(subdivision.get()); } - + public void initWithAddress(Address address) { this.address = address; - street.set(address.getStreet()); city.set(address.getCity()); postalCode.set(address.getPostalcode()); - + if (address.getCountry() != null) { selectedCountry.set(address.getCountry().getName()); } @@ -157,16 +177,17 @@ public void initWithAddress(Address address) { selectedSubdivision.set(address.getSubdivision().getName()); } } - + /** - * Creates an observable list that always has {@link #NOTHING_SELECTED_MARKER} as first element and the values of - * the given observable list. + * Creates an observable list that always has + * {@link #NOTHING_SELECTED_MARKER} as first element and the values of the + * given observable list. */ static ObservableList createListWithNothingSelectedMarker(ObservableList source) { final ObservableList result = FXCollections.observableArrayList(); result.add(NOTHING_SELECTED_MARKER); result.addAll(source); - + // for sure there are better solutions for this but it's sufficient for our demo source.addListener((ListChangeListener) c -> { result.clear(); @@ -175,57 +196,52 @@ static ObservableList createListWithNothingSelectedMarker(ObservableList }); return result; } - - public ReadOnlyBooleanProperty validProperty() { - return valid.getReadOnlyProperty(); - } - - + public ObservableList countriesList() { return countries; } - + public ObservableList subdivisionsList() { return subdivisions; } - + public StringProperty streetProperty() { return street; } - + public StringProperty cityProperty() { return city; } - + public StringProperty postalCodeProperty() { return postalCode; } - + public StringProperty selectedCountryProperty() { return selectedCountry; } - + public StringProperty selectedSubdivisionProperty() { return selectedSubdivision; } - + public ReadOnlyStringProperty subdivisionLabel() { return subdivisionLabel.getReadOnlyProperty(); } - + public ReadOnlyBooleanProperty loadingInProgressProperty() { return loadingInProgress.getReadOnlyProperty(); } - + public ReadOnlyBooleanProperty countryInputDisabledProperty() { return countryInputDisabled.getReadOnlyProperty(); } - + public ReadOnlyBooleanProperty subdivisionInputDisabledProperty() { return subdivisionInputDisabled.getReadOnlyProperty(); } - - public void resetForm() { + + private void resetForm() { street.set(""); city.set(""); postalCode.set(""); @@ -234,4 +250,5 @@ public void resetForm() { subdivision.set(null); country.set(null); } + } diff --git a/examples/contacts-example/src/main/resources/de/saxsys/mvvmfx/examples/contacts/ui/contactdialog/ContactDialogView.fxml b/examples/contacts-example/src/main/java/de/saxsys/mvvmfx/examples/contacts/ui/contactdialog/ContactDialogView.fxml similarity index 100% rename from examples/contacts-example/src/main/resources/de/saxsys/mvvmfx/examples/contacts/ui/contactdialog/ContactDialogView.fxml rename to examples/contacts-example/src/main/java/de/saxsys/mvvmfx/examples/contacts/ui/contactdialog/ContactDialogView.fxml diff --git a/examples/contacts-example/src/main/java/de/saxsys/mvvmfx/examples/contacts/ui/contactdialog/ContactDialogView.java b/examples/contacts-example/src/main/java/de/saxsys/mvvmfx/examples/contacts/ui/contactdialog/ContactDialogView.java index faee01cb2..fb00da16d 100644 --- a/examples/contacts-example/src/main/java/de/saxsys/mvvmfx/examples/contacts/ui/contactdialog/ContactDialogView.java +++ b/examples/contacts-example/src/main/java/de/saxsys/mvvmfx/examples/contacts/ui/contactdialog/ContactDialogView.java @@ -1,56 +1,59 @@ package de.saxsys.mvvmfx.examples.contacts.ui.contactdialog; -import javafx.fxml.FXML; -import javafx.scene.control.Button; -import javafx.scene.control.ContentDisplay; -import javafx.scene.control.Pagination; -import javafx.scene.text.Text; - import de.jensd.fx.fontawesome.AwesomeDude; import de.jensd.fx.fontawesome.AwesomeIcon; +import de.saxsys.mvvmfx.Context; import de.saxsys.mvvmfx.FluentViewLoader; import de.saxsys.mvvmfx.FxmlView; +import de.saxsys.mvvmfx.InjectContext; import de.saxsys.mvvmfx.InjectViewModel; import de.saxsys.mvvmfx.ViewTuple; import de.saxsys.mvvmfx.examples.contacts.ui.addressform.AddressFormView; import de.saxsys.mvvmfx.examples.contacts.ui.addressform.AddressFormViewModel; import de.saxsys.mvvmfx.examples.contacts.ui.contactform.ContactFormView; import de.saxsys.mvvmfx.examples.contacts.ui.contactform.ContactFormViewModel; +import javafx.fxml.FXML; +import javafx.scene.control.Button; +import javafx.scene.control.ContentDisplay; +import javafx.scene.control.Pagination; +import javafx.scene.text.Text; public class ContactDialogView implements FxmlView { - + @FXML private Button okButton; - + @FXML private Button previousButton; - + @FXML private Button nextButton; - + @FXML private Text titleText; - + @FXML private Pagination formPagination; - - + @InjectViewModel private ContactDialogViewModel viewModel; - - + + @InjectContext + private Context context; + public void initialize() { ViewTuple contactFormTuple = FluentViewLoader - .fxmlView(ContactFormView.class).load(); - + .fxmlView(ContactFormView.class) + .context(context) + .load(); + ViewTuple addressFormTuple = FluentViewLoader - .fxmlView(AddressFormView.class).load(); - - viewModel.setContactFormViewModel(contactFormTuple.getViewModel()); - viewModel.setAddressFormViewModel(addressFormTuple.getViewModel()); - + .fxmlView(AddressFormView.class) + .context(context) + .load(); + formPagination.getStyleClass().add("invisible-pagination-control"); - + formPagination.setPageFactory(index -> { if (index == 0) { return contactFormTuple.getView(); @@ -58,44 +61,45 @@ public void initialize() { return addressFormTuple.getView(); } }); - + formPagination.currentPageIndexProperty().bindBidirectional(viewModel.dialogPageProperty()); - + AwesomeDude.setIcon(okButton, AwesomeIcon.CHECK); AwesomeDude.setIcon(nextButton, AwesomeIcon.CHEVRON_RIGHT, ContentDisplay.RIGHT); AwesomeDude.setIcon(previousButton, AwesomeIcon.CHEVRON_LEFT); - + okButton.disableProperty().bind(viewModel.okButtonDisabledProperty()); okButton.visibleProperty().bind(viewModel.okButtonVisibleProperty()); okButton.managedProperty().bind(viewModel.okButtonVisibleProperty()); - + nextButton.disableProperty().bind(viewModel.nextButtonDisabledProperty()); nextButton.visibleProperty().bind(viewModel.nextButtonVisibleProperty()); nextButton.managedProperty().bind(viewModel.nextButtonVisibleProperty()); - + previousButton.disableProperty().bind(viewModel.previousButtonDisabledProperty()); previousButton.visibleProperty().bind(viewModel.previousButtonVisibleProperty()); previousButton.managedProperty().bind(viewModel.previousButtonVisibleProperty()); - + titleText.textProperty().bind(viewModel.titleTextProperty()); } - + @FXML private void previous() { viewModel.previousAction(); } - + @FXML private void next() { viewModel.nextAction(); } - + @FXML private void ok() { viewModel.okAction(); } - + public ContactDialogViewModel getViewModel() { return viewModel; } + } diff --git a/examples/contacts-example/src/main/java/de/saxsys/mvvmfx/examples/contacts/ui/contactdialog/ContactDialogViewModel.java b/examples/contacts-example/src/main/java/de/saxsys/mvvmfx/examples/contacts/ui/contactdialog/ContactDialogViewModel.java index 01c7905e4..37c37b6b2 100644 --- a/examples/contacts-example/src/main/java/de/saxsys/mvvmfx/examples/contacts/ui/contactdialog/ContactDialogViewModel.java +++ b/examples/contacts-example/src/main/java/de/saxsys/mvvmfx/examples/contacts/ui/contactdialog/ContactDialogViewModel.java @@ -1,126 +1,89 @@ package de.saxsys.mvvmfx.examples.contacts.ui.contactdialog; +import de.saxsys.mvvmfx.InjectScope; +import de.saxsys.mvvmfx.ViewModel; +import de.saxsys.mvvmfx.examples.contacts.ui.scopes.ContactDialogScope; import javafx.beans.binding.Bindings; -import javafx.beans.binding.BooleanBinding; -import javafx.beans.property.*; +import javafx.beans.property.IntegerProperty; +import javafx.beans.property.ReadOnlyBooleanProperty; +import javafx.beans.property.ReadOnlyBooleanWrapper; +import javafx.beans.property.SimpleIntegerProperty; +import javafx.beans.property.SimpleStringProperty; +import javafx.beans.property.StringProperty; import javafx.beans.value.ObservableBooleanValue; -import de.saxsys.mvvmfx.ViewModel; -import de.saxsys.mvvmfx.examples.contacts.ui.addressform.AddressFormViewModel; -import de.saxsys.mvvmfx.examples.contacts.ui.contactform.ContactFormViewModel; - public class ContactDialogViewModel implements ViewModel { - - private IntegerProperty dialogPage = new SimpleIntegerProperty(0); - - private ObjectProperty contactFormViewModel = new SimpleObjectProperty<>(); - private ObjectProperty addressFormViewModel = new SimpleObjectProperty<>(); - - private ReadOnlyBooleanWrapper valid = new ReadOnlyBooleanWrapper(); - - private StringProperty titleText = new SimpleStringProperty(); - - private Runnable okAction; - - // we need this field to prevent the binding from beeing garbage collected - private BooleanBinding viewModelsInitialized; - - public ContactDialogViewModel() { - viewModelsInitialized = contactFormViewModel.isNotNull().and(addressFormViewModel.isNotNull()); - - // as soon as both viewModels are set we add a binding that is true only when both viewModels are valid. - viewModelsInitialized.addListener((obs, oldV, newV) -> { - if (newV) { - valid.bind(Bindings.and(contactFormViewModel.get().validProperty(), addressFormViewModel.get() - .validProperty())); - } else { - valid.unbind(); - } - }); + + @InjectScope + ContactDialogScope dialogScope; + + private final IntegerProperty dialogPage = new SimpleIntegerProperty(0); + private final ReadOnlyBooleanWrapper valid = new ReadOnlyBooleanWrapper(); + private final StringProperty titleText = new SimpleStringProperty(); + + public void initialize() { + valid.bind( + Bindings.and(dialogScope.contactFormValidProperty(), dialogScope.addressFormValidProperty())); + dialogScope.bothFormsValidProperty().bind(valid); + dialogScope.subscribe(ContactDialogScope.RESET_DIALOG_PAGE, + (key, payload) -> resetDialogPage()); + titleText.bind(dialogScope.dialogTitleProperty()); } - + public void okAction() { - if (okAction != null) { - okAction.run(); - } - } - - public void setOkAction(Runnable okAction) { - this.okAction = okAction; + dialogScope.publish(ContactDialogScope.OK_BEFORE_COMMIT); } - + public void previousAction() { if (dialogPage.get() == 1) { dialogPage.set(0); } } - + public void nextAction() { if (dialogPage.get() == 0) { dialogPage.set(1); } } - - public void resetDialogPage() { + + private void resetDialogPage() { dialogPage.set(0); } - - public void resetForms() { - contactFormViewModel.get().resetForm(); - addressFormViewModel.get().resetForm(); - } - - public void setContactFormViewModel(ContactFormViewModel contactFormViewModel) { - this.contactFormViewModel.set(contactFormViewModel); - } - - public ContactFormViewModel getContactFormViewModel() { - return contactFormViewModel.get(); - } - - public void setAddressFormViewModel(AddressFormViewModel addressFormViewModel) { - this.addressFormViewModel.set(addressFormViewModel); - } - - public AddressFormViewModel getAddressFormViewModel() { - return addressFormViewModel.get(); - } - + public IntegerProperty dialogPageProperty() { return dialogPage; } - - + public ObservableBooleanValue okButtonDisabledProperty() { - return Bindings.and(contactFormViewModel.get().validProperty(), addressFormViewModel.get().validProperty()) - .not(); + return valid.not(); } - + public ObservableBooleanValue okButtonVisibleProperty() { return dialogPage.isEqualTo(1); } - + public ObservableBooleanValue nextButtonDisabledProperty() { - return contactFormViewModel.get().validProperty().not(); + return dialogScope.contactFormValidProperty().not(); } - + public ObservableBooleanValue nextButtonVisibleProperty() { return dialogPage.isEqualTo(0); } - + public ObservableBooleanValue previousButtonVisibleProperty() { return dialogPage.isEqualTo(1); } - + public ObservableBooleanValue previousButtonDisabledProperty() { - return addressFormViewModel.get().validProperty().not(); + return dialogScope.addressFormValidProperty().not(); } - + public ReadOnlyBooleanProperty validProperty() { return valid; } - + public StringProperty titleTextProperty() { return titleText; } + } diff --git a/examples/contacts-example/src/main/resources/de/saxsys/mvvmfx/examples/contacts/ui/contactform/ContactFormView.fxml b/examples/contacts-example/src/main/java/de/saxsys/mvvmfx/examples/contacts/ui/contactform/ContactFormView.fxml similarity index 100% rename from examples/contacts-example/src/main/resources/de/saxsys/mvvmfx/examples/contacts/ui/contactform/ContactFormView.fxml rename to examples/contacts-example/src/main/java/de/saxsys/mvvmfx/examples/contacts/ui/contactform/ContactFormView.fxml diff --git a/examples/contacts-example/src/main/java/de/saxsys/mvvmfx/examples/contacts/ui/contactform/ContactFormView.java b/examples/contacts-example/src/main/java/de/saxsys/mvvmfx/examples/contacts/ui/contactform/ContactFormView.java index bac28f484..3b0e03250 100644 --- a/examples/contacts-example/src/main/java/de/saxsys/mvvmfx/examples/contacts/ui/contactform/ContactFormView.java +++ b/examples/contacts-example/src/main/java/de/saxsys/mvvmfx/examples/contacts/ui/contactform/ContactFormView.java @@ -9,7 +9,7 @@ import javafx.scene.control.TextField; public class ContactFormView implements FxmlView { - + @FXML public TextField firstnameInput; @FXML @@ -28,12 +28,12 @@ public class ContactFormView implements FxmlView { public TextField phoneNumberInput; @FXML public DatePicker birthdayInput; - + private ValidationVisualizer validationVisualizer = new ControlsFxVisualizer(); - + @InjectViewModel private ContactFormViewModel viewModel; - + public void initialize() { firstnameInput.textProperty().bindBidirectional(viewModel.firstnameProperty()); lastnameInput.textProperty().bindBidirectional(viewModel.lastnameProperty()); @@ -44,7 +44,7 @@ public void initialize() { phoneNumberInput.textProperty().bindBidirectional(viewModel.phoneNumberProperty()); emailInput.textProperty().bindBidirectional(viewModel.emailProperty()); birthdayInput.valueProperty().bindBidirectional(viewModel.birthdayProperty()); - + validationVisualizer.initVisualization(viewModel.firstnameValidation(), firstnameInput, true); validationVisualizer.initVisualization(viewModel.lastnameValidation(), lastnameInput, true); validationVisualizer.initVisualization(viewModel.birthdayValidation(), birthdayInput); @@ -52,9 +52,9 @@ public void initialize() { validationVisualizer.initVisualization(viewModel.phoneValidation(), phoneNumberInput); validationVisualizer.initVisualization(viewModel.mobileValidation(), mobileNumberInput); } - + public ContactFormViewModel getViewModel() { return viewModel; } - + } diff --git a/examples/contacts-example/src/main/java/de/saxsys/mvvmfx/examples/contacts/ui/contactform/ContactFormViewModel.java b/examples/contacts-example/src/main/java/de/saxsys/mvvmfx/examples/contacts/ui/contactform/ContactFormViewModel.java index fafb48686..65a4ef10a 100644 --- a/examples/contacts-example/src/main/java/de/saxsys/mvvmfx/examples/contacts/ui/contactform/ContactFormViewModel.java +++ b/examples/contacts-example/src/main/java/de/saxsys/mvvmfx/examples/contacts/ui/contactform/ContactFormViewModel.java @@ -1,49 +1,58 @@ package de.saxsys.mvvmfx.examples.contacts.ui.contactform; +import java.time.LocalDate; + +import de.saxsys.mvvmfx.InjectScope; import de.saxsys.mvvmfx.ViewModel; import de.saxsys.mvvmfx.examples.contacts.model.Contact; +import de.saxsys.mvvmfx.examples.contacts.ui.scopes.ContactDialogScope; import de.saxsys.mvvmfx.examples.contacts.ui.validators.BirthdayValidator; import de.saxsys.mvvmfx.examples.contacts.ui.validators.EmailValidator; import de.saxsys.mvvmfx.examples.contacts.ui.validators.PhoneValidator; import de.saxsys.mvvmfx.utils.mapping.ModelWrapper; -import de.saxsys.mvvmfx.utils.validation.*; -import javafx.beans.binding.BooleanExpression; +import de.saxsys.mvvmfx.utils.validation.CompositeValidator; +import de.saxsys.mvvmfx.utils.validation.FunctionBasedValidator; +import de.saxsys.mvvmfx.utils.validation.ValidationMessage; +import de.saxsys.mvvmfx.utils.validation.ValidationStatus; +import de.saxsys.mvvmfx.utils.validation.Validator; +import javafx.beans.property.ObjectProperty; import javafx.beans.property.Property; import javafx.beans.property.StringProperty; -import java.time.LocalDate; - public class ContactFormViewModel implements ViewModel { - private ModelWrapper contactWrapper = new ModelWrapper<>(); - + + private final ModelWrapper contactWrapper = new ModelWrapper<>(); + private Validator firstnameValidator; private Validator lastnameValidator; - private Validator emailValidator = new EmailValidator(emailProperty()); - private Validator birthdayValidator = new BirthdayValidator(birthdayProperty()); - - private Validator phoneValidator = new PhoneValidator(phoneNumberProperty(), "The phone number is invalid!"); - private Validator mobileValidator = new PhoneValidator(mobileNumberProperty(), "The mobile number is invalid!"); - - private CompositeValidator formValidator = new CompositeValidator(); - + private final Validator emailValidator = new EmailValidator(emailProperty()); + private final Validator birthdayValidator = new BirthdayValidator(birthdayProperty()); + + private final Validator phoneValidator = new PhoneValidator(phoneNumberProperty(), "The phone number is invalid!"); + private final Validator mobileValidator = new PhoneValidator(mobileNumberProperty(), + "The mobile number is invalid!"); + + private final CompositeValidator formValidator = new CompositeValidator(); + + @InjectScope + ContactDialogScope dialogScope; + public ContactFormViewModel() { firstnameValidator = new FunctionBasedValidator<>( firstnameProperty(), firstName -> firstName != null && !firstName.trim().isEmpty(), ValidationMessage.error("Firstname may not be empty")); - - + lastnameValidator = new FunctionBasedValidator<>(lastnameProperty(), lastName -> { if (lastName == null || lastName.isEmpty()) { return ValidationMessage.error("Lastname may not be empty"); } else if (lastName.trim().isEmpty()) { return ValidationMessage.error("Lastname may not only contain whitespaces"); } - + return null; }); - - + formValidator.addValidators( firstnameValidator, lastnameValidator, @@ -52,88 +61,100 @@ public ContactFormViewModel() { phoneValidator, mobileValidator); } - - public void resetForm() { + + public void initialize() { + dialogScope.subscribe(ContactDialogScope.RESET_FORMS, (key, payload) -> resetForm()); + dialogScope.subscribe(ContactDialogScope.COMMIT, (key, payload) -> commitChanges()); + + ObjectProperty contactToEditProperty = dialogScope.contactToEditProperty(); + if (contactToEditProperty.get() != null) { + initWithContact(contactToEditProperty.get()); + } + + contactToEditProperty.addListener((observable, oldValue, newValue) -> { + if (newValue != null) { + initWithContact(newValue); + } + }); + + dialogScope.contactFormValidProperty().bind(formValidator.getValidationStatus().validProperty()); + } + + private void resetForm() { contactWrapper.reset(); } - - public void initWithContact(Contact contact) { + + private void initWithContact(Contact contact) { this.contactWrapper.set(contact); this.contactWrapper.reload(); } - - public Contact getContact() { - + + private void commitChanges() { if (contactWrapper.get() == null) { contactWrapper.set(new Contact()); } - + contactWrapper.commit(); - - return contactWrapper.get(); } - + public ValidationStatus firstnameValidation() { return firstnameValidator.getValidationStatus(); } - + public ValidationStatus lastnameValidation() { return lastnameValidator.getValidationStatus(); } - + public ValidationStatus birthdayValidation() { return birthdayValidator.getValidationStatus(); } - + public ValidationStatus emailValidation() { return emailValidator.getValidationStatus(); } - + public ValidationStatus phoneValidation() { return phoneValidator.getValidationStatus(); } - + public ValidationStatus mobileValidation() { return mobileValidator.getValidationStatus(); } - + public StringProperty firstnameProperty() { return contactWrapper.field("firstname", Contact::getFirstname, Contact::setFirstname); } - + public StringProperty titleProperty() { return contactWrapper.field("title", Contact::getTitle, Contact::setTitle); } - + public StringProperty lastnameProperty() { return contactWrapper.field("lastname", Contact::getLastname, Contact::setLastname); } - + public StringProperty roleProperty() { return contactWrapper.field("role", Contact::getRole, Contact::setRole); } - + public StringProperty departmentProperty() { return contactWrapper.field("department", Contact::getDepartment, Contact::setDepartment); } - + public Property birthdayProperty() { return contactWrapper.field("birthday", Contact::getBirthday, Contact::setBirthday); } - + public StringProperty emailProperty() { return contactWrapper.field("email", Contact::getEmailAddress, Contact::setEmailAddress); } - + public StringProperty mobileNumberProperty() { return contactWrapper.field("mobileNumber", Contact::getMobileNumber, Contact::setMobileNumber); } - + public StringProperty phoneNumberProperty() { return contactWrapper.field("phoneNumber", Contact::getPhoneNumber, Contact::setPhoneNumber); } - - public BooleanExpression validProperty() { - return formValidator.getValidationStatus().validProperty(); - } + } diff --git a/examples/contacts-example/src/main/resources/de/saxsys/mvvmfx/examples/contacts/ui/detail/DetailView.fxml b/examples/contacts-example/src/main/java/de/saxsys/mvvmfx/examples/contacts/ui/detail/DetailView.fxml similarity index 58% rename from examples/contacts-example/src/main/resources/de/saxsys/mvvmfx/examples/contacts/ui/detail/DetailView.fxml rename to examples/contacts-example/src/main/java/de/saxsys/mvvmfx/examples/contacts/ui/detail/DetailView.fxml index 970dd9da0..efa0c16c1 100644 --- a/examples/contacts-example/src/main/resources/de/saxsys/mvvmfx/examples/contacts/ui/detail/DetailView.fxml +++ b/examples/contacts-example/src/main/java/de/saxsys/mvvmfx/examples/contacts/ui/detail/DetailView.fxml @@ -13,38 +13,35 @@ - + - + - - + - + -