Components can be used inside other controllers to reduce the complexity of a single controller by moving some features into subcomponents (also called subcomponents). The framework supports subcomponents in the form of components. Basic controllers are not supported as subcomponents.
Subcomponents can be added to a controller by creating a field of the required type and annotating it with @SubComponent
.
The instance still has to be provided by the user, for example by using dependency injection.
@Controller
public class TodoController {
@FXML
VBox container; // VBox is specified in the FXML file
@SubComponent
@Inject
TodoListComponent todoListComponent;
// ...
@OnRender
public void render() {
container.getChildren().add(todoListComponent); // Add the subcomponent to the view
}
}
Components annotated with @SubComponent
will be automatically initialized and rendered along with the parent controller.
Subcomponents can be used in the same way as normal controllers, meaning they can have their own subcomponents and
initialize/render methods.
Subcomponents can also be specified in FXML files. In order to display a subcomponent, open the FXML file of the parent controller and add the subcomponent as an element like this:
<?import io.github.sekassel.jfxexample.controller.TodoController?>
...
<TodoController fx:id="yourid" onAction="onActionMethod" />
Depending on the parent you extended from, all attributes/properties available for this parent can be set for your custom component element as well.
This will use one of the fields annotated with @SubComponent
to display the subcomponent. The field has to be of the
same type as the component specified in the FXML file. Another way of providing the subcomponents to the FXML file
is by creating a Provider
of the subcomponent type and annotating it with @SubComponent
. When looking for a suitable
subcomponent, the framework will first check the fields and then the providers.
If the fx:id
attribute is specified, JavaFX will automatically inject the subcomponent instance back into the field.
If for example there are two subcomponents in the FXML file, the parent controller could have the following fields:
<DiceComponent/>
<DiceComponent/>
@Subcomponent
@Inject // This provider will be used to create the subcomponents
Provider<DiceComponent> diceCompProvider;
@Subcomponent
@Inject // This provider will be used to create the subcomponents
Provider<DiceComponent> diceCompProvider;
@FXML // This field will be injected by JavaFX
DiceComponent dice1;
@FXML // This field will be injected by JavaFX
DiceComponent dice2;
@Subcomponent
@Inject
@FXML // This field will be used as the instance for one subcomponent
DiceComponent dice1;
@Subcomponent
@Inject
@FXML // This field will be used as the instance for the other subcomponent
DiceComponent dice2;
There might be situations where you need a variable amount of subcomponents but constructs like a For-Loop is not suitable. In this case you can also create or provide your own instance(s) and initialize/render it manually.
Either inject a Provider<T>
and call get()
or create/inject a new instance of the component manually.
After acquiring the instance, you can initialize and render it manually using the initAndRender
method of the FulibFxApp
class.
This method takes the component, a map of parameters and a container disposable (e.g. ComponentDisposable or Subscriber).
The method will return the rendered instance of the component. If a disposable has been provided, a cleanup task for this component will be added to it.
Otherwise, one has to manually clean up the component by calling the destroy
method in the FulibFxApp
class.
@Controller
public class TodoController {
@Inject
App app; // App is the class extending FulibFxApp
@FXML
VBox container; // VBox is specified in the FXML file
@Inject
Provider<TodoListComponent> todoListComponentProvider;
@Inject
@SubComponent // This component will be framework
TodoManagerComponent todoManagerComponent;
@Inject // Do not use @SubComponent annotation, as we want to manage the subcomponent manually
TodoInputComponent todoInputComponent;
@Inject
Subscriber subscriber;
// ...
@OnRender
public void render() {
TodoListComponent result = app.initAndRender(todoListComponentProvider.get(), Map.of("param", value), subscriber); // This subcomponent has to be cleaned up manually by disposing the subscriber
container.getChildren().add(result); // Add the subcomponent to the view
// This subcomponent has to be cleaned up manually, e.g. by calling destroy in the app
container.getChildren().add(app.initAndRender(todoInputComponent));
// This subcomponent will be created based on the route and cleaned up by the subscriber
TodoTaskBarComponent taskBar = app.initAndRender("/todo/taskbar", Map.of("param", value), subscriber);
}
}