io.datafx.controller.ViewFactory Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of flow Show documentation
Show all versions of flow Show documentation
Visualizing Enterprise Data in JavaFX
/**
* Copyright (c) 2011, 2014, Jonathan Giles, Johan Vos, Hendrik Ebbers
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of DataFX, the website javafxdata.org, nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL DATAFX BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package io.datafx.controller;
import javafx.collections.ObservableMap;
import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.scene.Node;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.control.Tab;
import javafx.scene.image.ImageView;
import javafx.stage.Stage;
import io.datafx.controller.context.ViewContext;
import io.datafx.controller.context.ViewMetadata;
import io.datafx.core.DataFXUtils;
import io.datafx.core.ExceptionHandler;
import javax.annotation.PostConstruct;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.List;
/**
* This class contains static methods to create views in the DataFX Container context.
* All views that will use DataFX controller or Flow features have to be created by this class.
* Normally a developer doesn't need to use this class because the Flow API will
* call the methods internally whenever a new view is created in a flow.
* If a developers doesn't want to use the flow API and only want to create a
* single view that uses DataFX controller features this class can be used to create the view.
*/
public class ViewFactory {
private static ViewFactory instance;
private ViewFactory() {
}
/**
* Returns the single instance of the ViewFactory class (singleton pattern)
*
* @return the ViewFactory singleton
*/
public static synchronized ViewFactory getInstance() {
if (instance == null) {
instance = new ViewFactory();
}
return instance;
}
/**
* Creates a new MVC based view by using the given controller class. The
* class needs a default constructor (no parameters) and a
* {@link FXMLController} annotation to link to the fxml file. You can skip
* the annotation if you want to use the controller API conventions. By
* doing so the fxml files has to be in the package as the controller and
* must fit to a naming convention (see {@link io.datafx.controller.FXMLController} for more
* informations). The method returns a {@link io.datafx.controller.context.ViewContext}. This is a
* wrapper around the view (view-node and controller) and can be used to
* register your datamodel to the view. The doc of {@link io.datafx.controller.context.ViewContext} will
* provide more information about this topic.
*
* @param controllerClass the class of the controller.
* @return a ViewContext that encapsulate the complete MVC
* @throws FxmlLoadException if the fxml file can not be loaded
*/
public ViewContext createByController(final Class controllerClass)
throws FxmlLoadException {
return createByController(controllerClass, null);
}
/**
* Creates a new MVC based view by using the given controller class. The
* class needs a default constructor (no parameters) and a
* {@link FXMLController} annotation to link to the fxml file. You can skip
* the annotation if you want to use the controller API conventions. By
* doing so the fxml files has to be in the package as the controller and
* must fit to a naming convention (see {@link io.datafx.controller.FXMLController} for more
* informations). The method returns a {@link io.datafx.controller.context.ViewContext}. This is a
* wrapper around the view (view-node and controller) and can be used to
* register your datamodel to the view. The doc of {@link io.datafx.controller.context.ViewContext} will
* provide more information about this topic. By using this method you can
* overwrite the path to your fxml file.
*
* @param controllerClass the class of the controller.
* @param fxmlName path to the fxml file that will be used for the generated MVC
* context
* @return a ViewContext that encapsulate the complete MVC
* @throws FxmlLoadException if the fxml file can not be loaded
*/
public ViewContext createByController(
final Class controllerClass, String fxmlName)
throws FxmlLoadException {
return createByController(controllerClass, fxmlName, new ViewConfiguration());
}
/**
* Creates a new MVC based view by using the given controller class.
* The class needs a default constructor (no parameters) and
* a {@link FXMLController} annotation to link to the fxml file.
* You can skip the annotation if you want to use the controller API conventions.
* By doing so the fxml files has to be in the package as the controller and
* must fit to a naming convention (see {@link io.datafx.controller.FXMLController}
* for more information). The method returns a
* {@link io.datafx.controller.context.ViewContext}.
* This is a wrapper around the view (view-node and controller) and can be
* used to register your datamodel to the view. The doc of
* {@link io.datafx.controller.context.ViewContext} will
* provide more information about this topic. By using this method you can
* overwrite the path to your fxml file.
* @param
* @param controllerClass
* @param fxmlName
* @param viewConfiguration The configuration for the view
* @param viewContextResources initial resources that will be added to the view context
* @return
* @throws io.datafx.controller.FxmlLoadException
*/
public ViewContext createByController(
final Class controllerClass, String fxmlName, ViewConfiguration viewConfiguration, Object... viewContextResources)
throws FxmlLoadException {
try {
// 1. Create an instance of the Controller
final T controller = controllerClass.newInstance();
ViewMetadata metadata = new ViewMetadata();
FXMLController controllerAnnotation = (FXMLController) controllerClass
.getAnnotation(FXMLController.class);
if (controllerAnnotation != null && !controllerAnnotation.title().isEmpty()) {
metadata.setTitle(controllerAnnotation.title());
}
if (controllerAnnotation != null && !controllerAnnotation.iconPath().isEmpty()) {
metadata.setGraphic(new ImageView(controllerClass.getResource(controllerAnnotation.iconPath()).toExternalForm()));
}
// 2. load the FXML and make sure the @FXML annotations are injected
FXMLLoader loader = createLoader(controller, fxmlName, viewConfiguration);
Node viewNode = (Node) loader.load();
ViewContext context = new ViewContext<>(viewNode,
controller, metadata, viewConfiguration, viewContextResources);
context.register(controller);
context.register("controller", controller);
injectFXMLNodes(context);
// 3. Resolve the @Inject points in the Controller and call
// @PostConstruct
context.getResolver().injectResources(controller);
for (final Method method : controller.getClass().getMethods()) {
if (method.isAnnotationPresent(PostConstruct.class)) {
method.invoke(controller);
}
}
return context;
} catch (Exception e) {
throw new FxmlLoadException(e);
}
}
/**
* Helper method that will show exactly one view in a stage.
* This method can be used to create a simple one view application or test a view by only some lines of code
* @param stage The stage in that the view will be shown
* @param controllerClass the class of the controller that defines the view
*/
public void showInStage(Stage stage, Class> controllerClass) throws FxmlLoadException {
Scene myScene = new Scene((Parent) createByController(controllerClass).getRootNode());
stage.setScene(myScene);
stage.show();
}
private FXMLLoader createLoader(final Object controller, String fxmlName, ViewConfiguration viewConfiguration)
throws FxmlLoadException {
Class> controllerClass = controller.getClass();
String foundFxmlName = getFxmlName(controllerClass);
if (fxmlName != null) {
foundFxmlName = fxmlName;
}
if (foundFxmlName == null) {
throw new FxmlLoadException("No FXML File specified!");
}
FXMLLoader fxmlLoader = new FXMLLoader(
controllerClass.getResource(foundFxmlName));
fxmlLoader.setBuilderFactory(viewConfiguration.getBuilderFactory());
fxmlLoader.setCharset(viewConfiguration.getCharset());
fxmlLoader.setResources(viewConfiguration.getResources());
fxmlLoader.setController(controller);
fxmlLoader.setControllerFactory(c -> controller);
return fxmlLoader;
}
private String getFxmlName(Class> controllerClass) {
String foundFxmlName = null;
if (controllerClass.getSimpleName().endsWith("Controller")) {
String nameByController = controllerClass.getSimpleName()
.substring(
0,
controllerClass.getSimpleName().length()
- "Controller".length())
+ ".fxml";
if (DataFXUtils.canAccess(controllerClass, nameByController)) {
foundFxmlName = nameByController;
}
}
FXMLController controllerAnnotation = (FXMLController) controllerClass
.getAnnotation(FXMLController.class);
if (controllerAnnotation != null) {
foundFxmlName = controllerAnnotation.value();
}
return foundFxmlName;
}
/**
* This methods creates a tab that contains the given view.
* By doing so the metadata of the view will be bound to the tab properties.
* So the text and icon of the tab can be changed by the view.
* @param controllerClass the class that defines the controller of the view
*/
public Tab createTab(Class controllerClass) throws FxmlLoadException {
return createTab(createByController(controllerClass));
}
/**
* This methods creates a tab that contains the given view.
* By doing so the metadata of the view will be bound to the tab properties.
* So the text and icon of the tab can be changed by the view.
* @param controllerClass the class that defines the controller of the view
* @param exceptionHandler the exception handle for the view. This handler will handle all exceptions that will be thrown in the DataFX container context
*/
public Tab createTab(Class controllerClass, ExceptionHandler exceptionHandler) throws FxmlLoadException {
return createTab(createByController(controllerClass), exceptionHandler);
}
/**
* This methods creates a tab that contains the given view.
* By doing so the metadata of the view will be bound to the tab properties.
* So the text and icon of the tab can be changed by the view.
*
* @param context the context of the view. This includes the view and its controller instance
* so that all needed informations are part of the context
*/
public Tab createTab(ViewContext context) {
return createTab(context, ExceptionHandler.getDefaultInstance());
}
/**
* This methods creates a tab that contains the given view.
* By doing so the metadata of the view will be bound to the tab properties.
* So the text and icon of the tab can be changed by the view.
*
* @param context the context of the view. This includes the view and its controller instance
* so that all needed informations are part of the context
* @param exceptionHandler the exception handle for the view. This handler will handle all exceptions that will be thrown in the DataFX container context
*/
public Tab createTab(ViewContext context, ExceptionHandler exceptionHandler) {
Tab tab = new Tab();
tab.textProperty().bind(context.getMetadata().titleProperty());
tab.graphicProperty().bind(context.getMetadata().graphicsProperty());
tab.setOnClosed(e -> {
try {
context.destroy();
} catch (Exception exception) {
exceptionHandler.setException(exception);
}
});
tab.setContent(context.getRootNode());
return tab;
}
/**
* Because of some restrictions in the FXMLLoader not all fields that are annotated with @FXML will be injected in
* a controller when using the FXMLLoader class. This helper methods injects all fields that were not injected by
* JavaFX basics.
* The following types will not be injected by FXMLLoader:
* - private fields in a superclass of the controller class
* - fields that defines a node that is part of a sub-fxml. This is a fxml definition that is included in the fxml
* file.
* @param context The context of the view
* @param Type of the controller
*/
private void injectFXMLNodes(ViewContext context) {
T controller = context.getController();
Node n = context.getRootNode();
List fields = DataFXUtils.getInheritedPrivateFields(controller.getClass());
for (Field field : fields) {
if (field.getAnnotation(FXML.class) != null) {
if (DataFXUtils.getPrivileged(field, controller) == null) {
if (Node.class.isAssignableFrom(field.getType())) {
Node toInject = n.lookup("#" + field.getName());
if(toInject != null) {
DataFXUtils.setPrivileged(field, controller, toInject);
}
}
}
}
}
}
}