diff --git a/examples/mvvmfx-cdi-starter/src/main/java/de/saxsys/jfx/Starter.java b/examples/mvvmfx-cdi-starter/src/main/java/de/saxsys/jfx/Starter.java index 782d595e8..4dfb9c1f0 100644 --- a/examples/mvvmfx-cdi-starter/src/main/java/de/saxsys/jfx/Starter.java +++ b/examples/mvvmfx-cdi-starter/src/main/java/de/saxsys/jfx/Starter.java @@ -9,6 +9,7 @@ import de.saxsys.jfx.exampleapplication.view.maincontainer.MainContainerView; import de.saxsys.jfx.exampleapplication.viewmodel.maincontainer.MainContainerViewModel; import de.saxsys.jfx.mvvm.cdi.MvvmfxCdiApplication; +import de.saxsys.jfx.mvvm.viewloader.FluentViewLoader; import de.saxsys.jfx.mvvm.viewloader.ViewLoader; import de.saxsys.jfx.mvvm.viewloader.ViewTuple; @@ -23,14 +24,10 @@ public static void main(String... args) { launch(args); } - @Inject - private ViewLoader viewLoader; - @Override public void start(Stage stage) { ViewTuple tuple = - viewLoader - .loadViewTuple(MainContainerView.class); + FluentViewLoader.fxmlView(MainContainerView.class).load(); Parent view = tuple.getView(); diff --git a/examples/mvvmfx-complex-example/src/main/java/de/saxsys/jfx/exampleapplication/view/maincontainer/MainContainerView.java b/examples/mvvmfx-complex-example/src/main/java/de/saxsys/jfx/exampleapplication/view/maincontainer/MainContainerView.java index 3fd5e2f41..8db29ff52 100644 --- a/examples/mvvmfx-complex-example/src/main/java/de/saxsys/jfx/exampleapplication/view/maincontainer/MainContainerView.java +++ b/examples/mvvmfx-complex-example/src/main/java/de/saxsys/jfx/exampleapplication/view/maincontainer/MainContainerView.java @@ -4,6 +4,8 @@ import java.util.HashMap; import java.util.Map; import java.util.ResourceBundle; + +import de.saxsys.jfx.mvvm.viewloader.FluentViewLoader; import javafx.beans.value.ChangeListener; import javafx.beans.value.ObservableValue; import javafx.fxml.FXML; @@ -46,10 +48,6 @@ public class MainContainerView implements FxmlView, Init // Inject the Code behind instance of the ListView private ListView personWelcomeListView; - @Inject - // ViewLoder - private ViewLoader viewLoader; - @Inject // Notification Center private NotificationCenter notificationCenter; @@ -93,8 +91,7 @@ public void changed(ObservableValue arg0, public ViewTuple map(Integer element) { if (!viewMap.containsKey(element)) { ViewTuple loadedViewTuple - = viewLoader - .loadViewTuple(PersonWelcomeView.class); + = FluentViewLoader.fxmlView(PersonWelcomeView.class).load(); PersonWelcomeView codeBehind = loadedViewTuple.getCodeBehind(); diff --git a/examples/mvvmfx-fx-root-example/src/main/java/de/saxsys/jfx/mvvmfx/fx_root_example/LabeledTextField.java b/examples/mvvmfx-fx-root-example/src/main/java/de/saxsys/jfx/mvvmfx/fx_root_example/LabeledTextField.java index cd5c90b7f..9b50901ad 100644 --- a/examples/mvvmfx-fx-root-example/src/main/java/de/saxsys/jfx/mvvmfx/fx_root_example/LabeledTextField.java +++ b/examples/mvvmfx-fx-root-example/src/main/java/de/saxsys/jfx/mvvmfx/fx_root_example/LabeledTextField.java @@ -1,5 +1,6 @@ package de.saxsys.jfx.mvvmfx.fx_root_example; +import de.saxsys.jfx.mvvm.viewloader.FluentViewLoader; import javafx.fxml.FXML; import javafx.scene.control.Button; import javafx.scene.control.Label; @@ -25,12 +26,7 @@ public class LabeledTextField extends HBox implements FxmlView tuple = - viewLoader - .loadViewTuple(MainContainerView.class); + FluentViewLoader.fxmlView(MainContainerView.class).load(); + // Locate View for loaded FXML file final Parent view = tuple.getView(); diff --git a/examples/mvvmfx-helloworld-without-fxml/src/main/java/de/saxsys/jfx/mvvmfx/helloworld/Starter.java b/examples/mvvmfx-helloworld-without-fxml/src/main/java/de/saxsys/jfx/mvvmfx/helloworld/Starter.java index 24b49ff01..1e2bcf9eb 100644 --- a/examples/mvvmfx-helloworld-without-fxml/src/main/java/de/saxsys/jfx/mvvmfx/helloworld/Starter.java +++ b/examples/mvvmfx-helloworld-without-fxml/src/main/java/de/saxsys/jfx/mvvmfx/helloworld/Starter.java @@ -1,6 +1,7 @@ package de.saxsys.jfx.mvvmfx.helloworld; +import de.saxsys.jfx.mvvm.viewloader.FluentViewLoader; import javafx.application.Application; import javafx.scene.Parent; import javafx.scene.Scene; @@ -21,10 +22,7 @@ public static void main(String... args) { public void start(Stage stage) throws Exception { stage.setTitle("Hello World Application"); - ViewLoader viewLoader = new ViewLoader(); - - ViewTuple viewTuple = viewLoader - .loadViewTuple(HelloWorldView.class); + ViewTuple viewTuple = FluentViewLoader.javaView(HelloWorldView.class).load(); Parent root = viewTuple.getView(); stage.setScene(new Scene(root)); diff --git a/examples/mvvmfx-helloworld/src/main/java/de/saxsys/jfx/mvvmfx/helloworld/Starter.java b/examples/mvvmfx-helloworld/src/main/java/de/saxsys/jfx/mvvmfx/helloworld/Starter.java index 0b7e7fb5c..da8c7f3ba 100644 --- a/examples/mvvmfx-helloworld/src/main/java/de/saxsys/jfx/mvvmfx/helloworld/Starter.java +++ b/examples/mvvmfx-helloworld/src/main/java/de/saxsys/jfx/mvvmfx/helloworld/Starter.java @@ -1,5 +1,6 @@ package de.saxsys.jfx.mvvmfx.helloworld; +import de.saxsys.jfx.mvvm.viewloader.FluentViewLoader; import javafx.application.Application; import javafx.scene.Parent; import javafx.scene.Scene; @@ -20,11 +21,8 @@ public static void main(String... args) { @Override public void start(Stage stage) throws Exception { stage.setTitle("Hello World Application"); - - ViewLoader viewLoader = new ViewLoader(); - - ViewTuple viewTuple = viewLoader - .loadViewTuple(HelloWorldView.class); + + ViewTuple viewTuple = FluentViewLoader.fxmlView(HelloWorldView.class).load(); Parent root = viewTuple.getView(); stage.setScene(new Scene(root)); diff --git a/mvvmfx/src/main/java/de/saxsys/jfx/mvvm/viewloader/FluentViewLoader.java b/mvvmfx/src/main/java/de/saxsys/jfx/mvvm/viewloader/FluentViewLoader.java new file mode 100644 index 000000000..89dad534e --- /dev/null +++ b/mvvmfx/src/main/java/de/saxsys/jfx/mvvm/viewloader/FluentViewLoader.java @@ -0,0 +1,138 @@ +package de.saxsys.jfx.mvvm.viewloader; + +import de.saxsys.jfx.mvvm.api.FxmlView; +import de.saxsys.jfx.mvvm.api.JavaView; +import de.saxsys.jfx.mvvm.api.ViewModel; +import de.saxsys.jfx.mvvm.base.view.View; +import net.jodah.typetools.TypeResolver; + +import java.util.ResourceBundle; + +/** + * Fluent API for loading Views. This is basically a wrapper around the {@link de.saxsys.jfx.mvvm.viewloader.ViewLoader} + * to get a better usable API. + * + * @author manuel.mauky + */ +public class FluentViewLoader { + + /** + * This class is the builder step to load a java based view. + * @param + * @param + */ + public static class JavaViewStep, ViewModelType extends ViewModel> { + + private Class viewType; + private ResourceBundle resourceBundle; + + JavaViewStep(Class viewType){ + this.viewType = viewType; + } + + /** + * @param resourceBundle the resource bundle that is used while loading the view. + * @return this instance of the builder step. + */ + public JavaViewStep resourceBundle(ResourceBundle resourceBundle){ + this.resourceBundle = resourceBundle; + return this; + } + + /** + * The final step of the Fluent API. This method loads the view based on the given params. + * + * @return a view tuple containing the loaded view. + */ + public ViewTuple load(){ + ViewLoader viewLoader = new ViewLoader(); + + return viewLoader.loadViewTuple(viewType, resourceBundle); + } + } + + /** + * This class is the builder step to load a fxml based view. + * @param + * @param + */ + public static class FxmlViewStep, ViewModelType extends ViewModel> { + + private Class viewType; + private ResourceBundle resourceBundle; + private Object root; + private ViewType codeBehind; + + FxmlViewStep(Class viewType){ + this.viewType = viewType; + } + + /** + * @param resourceBundle the resource bundle that is used while loading the view. + * @return this instance of the builder step. + */ + public FxmlViewStep resourceBundle(ResourceBundle resourceBundle){ + this.resourceBundle = resourceBundle; + return this; + } + + /** + * This param is used to define a JavaFX node that is used as the root element + * when loading the fxml file. + *
+ * + * This can be useful when creating custom controls with the fx:root element. + * + * @param root the root element that is used to load the fxml file. + * @return this instance of the builder step. + */ + public FxmlViewStep root(Object root){ + this.root = root; + return this; + } + + /** + * This param is used to define an existing instance of the codeBehind class that + * is used instead of creating a new one while loading. + *
+ * + * This can be useful when creating custom controls with the fx:root element. + * + * @param codeBehind the codeBehind instance that is used to load the fxml file. + * @return this instance of the builder step. + */ + public FxmlViewStep codeBehind(ViewType codeBehind){ + this.codeBehind = codeBehind; + return this; + } + + /** + * The final step of the Fluent API. This method loads the view based on the given params. + * + * @return a view tuple containing the loaded view. + */ + public ViewTuple load(){ + ViewLoader viewLoader = new ViewLoader(); + + return viewLoader.loadViewTuple(viewType, resourceBundle, codeBehind, root); + } + } + + + /** + * This method is the entry point of the Fluent API to load a java based view. + */ + public static , ViewModelType extends ViewModel> + JavaViewStep javaView(Class viewType){ + return new JavaViewStep<>(viewType); + } + + /** + * This method is the entry point of the Fluent API to load a fxml based View. + */ + public static , ViewModelType extends ViewModel> + FxmlViewStep fxmlView(Class viewType){ + return new FxmlViewStep<>(viewType); + } + +} diff --git a/mvvmfx/src/main/java/de/saxsys/jfx/mvvm/viewloader/ViewLoader.java b/mvvmfx/src/main/java/de/saxsys/jfx/mvvm/viewloader/ViewLoader.java index eea2ca247..2693262bb 100644 --- a/mvvmfx/src/main/java/de/saxsys/jfx/mvvm/viewloader/ViewLoader.java +++ b/mvvmfx/src/main/java/de/saxsys/jfx/mvvm/viewloader/ViewLoader.java @@ -29,20 +29,17 @@ import de.saxsys.jfx.mvvm.base.view.View; /** - * Loader class for loading FXML and code behind from Fs. There are following options for loading the FXML: - *

- *

    - *
  • Providing the code behind class (controller) by calling {@link #loadViewTuple(Class)}
  • - *
  • Providing a path to the FXML file by calling {@link #loadViewTuple(String)}
  • - *
+ * This class can be used to load MvvmFX views. It provides overloaded loading methods that return {@link de.saxsys.jfx.mvvm.viewloader.ViewTuple} + * instances that contain the loaded view. + * + * Instead of using this class you can also use the {@link de.saxsys.jfx.mvvm.viewloader.FluentViewLoader} + * to load views. It provides a fluent api to specify your params instead of using overloaded methods of this class. + * The usage of the {@link de.saxsys.jfx.mvvm.viewloader.FluentViewLoader} is the recommended way of using MvvmFX. * * @author alexander.casall, manuel.mauky */ public final class ViewLoader { - private Object codeBehind; - private Object root; - private static final Logger LOG = LoggerFactory.getLogger(ViewLoader.class); private final FxmlViewLoader fxmlViewLoader = new FxmlViewLoader(); @@ -82,6 +79,60 @@ public , ViewModelType extends Vi */ public , ViewModelType extends ViewModel> ViewTuple loadViewTuple( Class viewType, ResourceBundle resourceBundle) { + + return loadViewTuple(viewType, resourceBundle, null, null); + + } + + + /** + * Load a ViewTuple for the given parameters. + *
+ * This method is useful if you need to define one of the additional not so common params like 'codeBehind' or + * 'root'. + *
+ * With the "codeBehind" param you can use an existing codeBehind instance for this view. This can be useful when + * you need to create the codeBehind class outside of the normal dependency injection mechanism or you like to reuse + * an existing instance for a second view. + *
+ * With the "root" param you can use an existing JavaFX node as the root for the loaded View. + *
+ * The most common use-case for both "codeBehind" and "root" params is when you like to create a custom component + * with MvvmFX with the fx:root element. In this case you will subclass from a JavaFX component and use the viewLoader in the constructor of + * your custom component. See the following example: + * + *
+	 * 
+	 *     public class MyComponent extends VBox implements FxmlView {@code} {
+	 *         
+	 *         public MyComponent(){
+	 *             ViewLoader viewLoader = new ViewLoader();
+	 *             viewLoader.loadViewTuple(this.getClass(), null, this, this);
+	 *         }
+	 *     }
+	 * 
+	 * 
+ * + * + * + * @param viewType + * the class type of the view to be loaded. + * @param resourceBundle + * the resourceBundle that is loaded with the view. This param is optional and can be null. + * @param codeBehind + * the codeBehind instance that will be used. This param is optional and can be null. When + * this param is null a new instance will be created / obtained by the dependency injection + * mechanism. + * @param root + * the javafx node that is used as the root element. This param is optional and can be null. + * @param + * the generic type of the View + * @param + * the generic type of the ViewModel + * @return a ViewTuple instance that contains the loaded view. + */ + public , ViewModelType extends ViewModel> ViewTuple loadViewTuple( + Class viewType, ResourceBundle resourceBundle, ViewType codeBehind, Object root) { Type type = TypeResolver.resolveGenericType(FxmlView.class, viewType); if (type != null) { @@ -104,24 +155,25 @@ public , ViewModelType extends Vi throw new IllegalArgumentException(errorMessage); } + /** * Load the view (Code behind + Node from FXML) by a given resource path. * - * @param resource + * @param fxmlPath * path to the FXML which should be loaded * * * @return the ViewTuple that contains the view and the viewModel. */ public , ViewModelType extends ViewModel> ViewTuple loadViewTuple( - final String resource) { - return loadViewTuple(resource, null); + final String fxmlPath) { + return loadViewTuple(fxmlPath, null); } /** * Load the view (Code behind + Node from FXML) by a given resource path. * - * @param resource + * @param fxmlPath * path to the FXML which should be loaded * @param resourceBundle * which is passed to the viewloader @@ -129,49 +181,7 @@ public , ViewModelType extends Vi * @return the ViewTuple that contains the view and the viewModel. */ public , ViewModelType extends ViewModel> ViewTuple loadViewTuple( - final String resource, ResourceBundle resourceBundle) { - return fxmlViewLoader.loadFxmlViewTuple(resource, resourceBundle, codeBehind, root); + final String fxmlPath, ResourceBundle resourceBundle) { + return fxmlViewLoader.loadFxmlViewTuple(fxmlPath, resourceBundle, null, null); } - - /** - * Returns the codeBehind instance. - * - * @return codeBehind - */ - @SuppressWarnings("unchecked") - public , ViewModelType extends ViewModel> ViewType getCodeBehind() { - // The codeBehind-field is of type Object but the only way to set this field is by the setter, which has a - // parameter of type `ViewType`. - // Therefore we know that the codeBehind can only be of the type `ViewType` and therefore this cast is safe. - return (ViewType) codeBehind; - } - - /** - * @see #getCodeBehind() - * - * @param codeBehind - * - */ - public , ViewModelType extends ViewModel> void setCodeBehind( - ViewType codeBehind) { - this.codeBehind = codeBehind; - } - - /** - * Returns the root of the loaded view. - * - * @return root - */ - public Object getRoot() { - return root; - } - - /** - * @see #getRoot() - * @param root - */ - public void setRoot(Object root) { - this.root = root; - } - } diff --git a/mvvmfx/src/test/java/de/saxsys/jfx/mvvm/viewloader/FluentViewLoaderTest.java b/mvvmfx/src/test/java/de/saxsys/jfx/mvvm/viewloader/FluentViewLoaderTest.java new file mode 100644 index 000000000..ea1d0f775 --- /dev/null +++ b/mvvmfx/src/test/java/de/saxsys/jfx/mvvm/viewloader/FluentViewLoaderTest.java @@ -0,0 +1,95 @@ +package de.saxsys.jfx.mvvm.viewloader; + +import static org.assertj.core.api.Assertions.*; + +import java.io.IOException; +import java.io.StringReader; +import java.util.PropertyResourceBundle; +import java.util.ResourceBundle; + +import de.saxsys.jfx.mvvm.viewloader.example.TestFxmlViewFxRoot; +import javafx.scene.layout.VBox; + +import org.junit.Test; + +import de.saxsys.jfx.mvvm.viewloader.example.TestFxmlView; +import de.saxsys.jfx.mvvm.viewloader.example.TestJavaView; +import de.saxsys.jfx.mvvm.viewloader.example.TestViewModel; + + +/** + * This test verifies the API of the {@link de.saxsys.jfx.mvvm.viewloader.FluentViewLoader}. The functionality of + * loading Views is not part of this test as it is already tested in other tests for the ViewLoader itself. + */ +public class FluentViewLoaderTest { + + + /// FXML VIEW /// + + @Test + public void testLoadFxmlView() { + ViewTuple viewTuple = FluentViewLoader.fxmlView(TestFxmlView.class).load(); + + assertThat(viewTuple.getCodeBehind()).isNotNull(); + assertThat(viewTuple.getViewModel()).isNotNull(); + assertThat(viewTuple.getView()).isInstanceOf(VBox.class); + } + + @Test + public void testLoadFxmlViewWithResourceBundle() throws IOException{ + final ResourceBundle resourceBundle = new PropertyResourceBundle(new StringReader("")); + + ViewTuple viewTuple = FluentViewLoader.fxmlView(TestFxmlView.class).resourceBundle(resourceBundle).load(); + assertThat(viewTuple).isNotNull(); + } + + + @Test + public void testLoadFxRoot(){ + TestFxmlViewFxRoot fxRoot = new TestFxmlViewFxRoot(); + + ViewTuple viewTuple = FluentViewLoader.fxmlView(TestFxmlViewFxRoot.class).codeBehind(fxRoot).root(fxRoot).load(); + + assertThat(viewTuple).isNotNull(); + assertThat(viewTuple.getCodeBehind()).isEqualTo(fxRoot); + assertThat(viewTuple.getView()).isEqualTo(fxRoot); + } + + @Test + public void testLoadFxmlViewWithAllParams() throws IOException{ + TestFxmlViewFxRoot customControl = new TestFxmlViewFxRoot(); + final ResourceBundle resourceBundle = new PropertyResourceBundle(new StringReader("")); + + ViewTuple viewTuple = FluentViewLoader.fxmlView(TestFxmlViewFxRoot.class) + .codeBehind(customControl) + .resourceBundle(resourceBundle) + .root(customControl) + .load(); + + assertThat(viewTuple).isNotNull(); + assertThat(viewTuple.getCodeBehind()).isEqualTo(customControl); + assertThat(viewTuple.getView()).isEqualTo(customControl); + + } + + + /// JAVA VIEW /// + + @Test + public void testLoadJavaView() { + ViewTuple viewTuple = FluentViewLoader.javaView(TestJavaView.class).load(); + + assertThat(viewTuple.getCodeBehind()).isNotNull(); + assertThat(viewTuple.getViewModel()).isNotNull(); + assertThat(viewTuple.getView()).isInstanceOf(VBox.class); + } + + @Test + public void testLoadJavaViewWithResourceBundle() throws IOException { + final ResourceBundle resourceBundle = new PropertyResourceBundle(new StringReader("")); + + ViewTuple viewTuple = FluentViewLoader.javaView(TestJavaView.class) + .resourceBundle(resourceBundle).load(); + assertThat(viewTuple).isNotNull(); + } +} diff --git a/mvvmfx/src/test/java/de/saxsys/jfx/mvvm/viewloader/ViewLoaderIntegrationTest.java b/mvvmfx/src/test/java/de/saxsys/jfx/mvvm/viewloader/ViewLoaderIntegrationTest.java index bd14d2b55..8fcc3966f 100644 --- a/mvvmfx/src/test/java/de/saxsys/jfx/mvvm/viewloader/ViewLoaderIntegrationTest.java +++ b/mvvmfx/src/test/java/de/saxsys/jfx/mvvm/viewloader/ViewLoaderIntegrationTest.java @@ -136,10 +136,8 @@ public void testUseExistingCodeBehind(){ TestFxmlViewWithMissingController codeBehind = new TestFxmlViewWithMissingController(); - viewLoader.setCodeBehind(codeBehind); - ViewTuple viewTuple = viewLoader - .loadViewTuple(TestFxmlViewWithMissingController.class); + .loadViewTuple(TestFxmlViewWithMissingController.class, null, codeBehind, null); assertThat(viewTuple).isNotNull(); @@ -156,11 +154,8 @@ public void testUseExistingCodeBehindFailWhenControllerIsDefinedInFXML() { try { TestFxmlView codeBehind = new TestFxmlView(); // the fxml file for this class has a fx:controller defined. - - viewLoader.setCodeBehind(codeBehind); - ViewTuple viewTuple = viewLoader - .loadViewTuple(TestFxmlView.class); + .loadViewTuple(TestFxmlView.class, null, codeBehind, null); fail("Expected a LoadException to be thrown"); }catch(Exception e) { @@ -183,10 +178,8 @@ public void testAlreadyExistingViewModelShouldNotBeOverwritten(){ codeBehind.viewModel = existingViewModel; - viewLoader.setCodeBehind(codeBehind); - ViewTuple viewTuple = viewLoader - .loadViewTuple(TestFxmlViewWithMissingController.class); + .loadViewTuple(TestFxmlViewWithMissingController.class, null, codeBehind, null); assertThat(viewTuple.getCodeBehind()).isNotNull(); assertThat(viewTuple.getCodeBehind().viewModel).isEqualTo(existingViewModel);