Use a custom MVC pattern with a lot of convenient tricks
The User Interface layer is composed by three main parts :
Each of these must do predefined tasks to maintain a good Separation of Concerns (SoC).
Models are JRebirth Components like Commands and Services, they are mandatory whereas Views and Controllers are optionals.
Short UML Diagram:
Model are JRebirth Components, they are retrievable from UiFacade and they can be dynamically attached to any placeholder.
They are able to listen Wave and to communicate with other components.
Models store business objects with custom method that allow to bound an object to a model and then to use its internal properties to bound them to UI widget.
Models' aim is to manage UI lifecycle and communication, you can place into some part of your business logic.
Views are automatically created by Models according to generic type used, their main objectives is to build and wrap the graphical root Node.
You can manage Model-View interaction in both direction: Model-to-View by adding some 'package' Node getters into the view (preferred way for binding declarations), or View-to-Model by adding some 'package' methods into Model (only for some call).
Controllers are dedicated to manage event handling by providing several ways to facilitate developers' life. They are automatically created by the view according to generic type used.
Event Handler can be attached from View-to-Controller, or from Controller-to-View (preferred way, to centralize event handlers declarations).
You can ask why do MVC objects use so much generics type that hurt eyes !! The reason is really simple: To avoid lot of cast ! When you want to call a method from another part you will have a method to grab the part with the right type, so no cast are needed. An alternative would be to write ourselves getter method with right cast but it's really painful and doesn't have any value. So you will write once this quite complex (especially when you use intermediate classes) class declaration with its generic type and you will enjoy coding without having to cast them.
User Interface layer is versatile and will adapt itself to your use case to avoid boiler plate code. Hereafter you will find a list of all possible ways to use a Model:
Models are directly synchronized with the UIFacade and can send & receive Waves, they can also use any other components.
The goal of Models is to retrieve data from other layers, and to define Business Logic (business rules, authorizations ...).
The Model automatically build its attached view (except SimpleModel).
13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 | public final class SampleModel extends DefaultModel<SampleModel, SampleView> { /** The class logger. */ private static final Logger LOGGER = LoggerFactory.getLogger(SampleModel. class ); /** * {@inheritDoc} */ @Override protected void initModel() { LOGGER.debug( "Init Sample Model" ); // Put the code to initialize your model here } /** * {@inheritDoc} */ @Override protected void initInnerComponents() { // Put the code to initialize inner models here (if any) } /** * {@inheritDoc} */ @Override protected void bind() { // Put the code to manage model object binding (if any) } /** * {@inheritDoc} */ @Override protected void processWave( final Wave wave) { // Process a wave action, you must listen the wave type before } } |
As explained before, the main goal of a View is to create the rootNode attached to the Model.
Fortunately the rootNode is automatically created according to the generic type used in the View header.
The View's initialization code perform several operations:
If the construction fails, mainly due to Controller error, a custom error Node is created in order to display the stack trace of the exception.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 | package org.jrebirth.af.sample.ui; import javafx.scene.control.Button; import javafx.scene.control.LabelBuilder; import javafx.scene.layout.BorderPane; import javafx.scene.layout.FlowPaneBuilder; import org.jrebirth.af.api.exception.CoreException; import org.jrebirth.af.api.ui.annotation.OnMouse; import org.jrebirth.af.api.ui.annotation.type.Mouse; import org.jrebirth.af.core.ui.AbstractView; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * The class <strong>SampleView</strong>. * * @author */ public final class SampleView extends AbstractView<SampleModel, BorderPane, SampleController> { /** The class logger. */ private static final Logger LOGGER = LoggerFactory.getLogger(SampleView. class ); /** Button used to trigger the SampleCommand. */ @OnMouse (Mouse.Clicked) private Button defaultCommand; /** Button used to trigger the SampleUICommand. */ private Button uiCommand; /** Button used to trigger the SamplePoolCommand. */ private Button pooledCommand; /** * Default Constructor. * * @param model the controls view model * * @throws CoreException if build fails */ public SampleView( final SampleModel model) throws CoreException { super (model); } /** * {@inheritDoc} */ @Override protected void initView() { this .defaultCommand = new Button( "Trigger a default Command into JIT" ); this .uiCommand = new Button( "Trigger an UI Command into JAT" ); this .pooledCommand = new Button( "Trigger a pooled Command into JTP" ); getRootNode().setCenter( LabelBuilder.create() .text( "JRebirth Sample" ) .build() ); getRootNode().setBottom(FlowPaneBuilder.create().children( this .defaultCommand, this .uiCommand, this .pooledCommand ).build()); } /** * {@inheritDoc} */ @Override public void start() { LOGGER.debug( "Start the Sample View" ); // Custom code to process when the view is displayed the first time } /** * {@inheritDoc} */ @Override public void reload() { LOGGER.debug( "Reload the Sample View" ); // Custom code to process when the view is displayed the 1+n time } /** * {@inheritDoc} */ @Override public void hide() { LOGGER.debug( "Hide the Sample View" ); // Custom code to process when the view is hidden } /** * Return the button that trigger the default command. * * @return the button that trigger the default command */ Button getDefaultCommand() { return this .defaultCommand; } /** * Return the button that trigger the UI command. * * @return the button that trigger the UI command */ Button getUiCommand() { return this .uiCommand; } /** * Return the button that trigger the pooled command. * * @return the button that trigger the pooled command */ Button getPooledCommand() { return this .pooledCommand; } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 | package org.jrebirth.af.sample.ui; import javafx.scene.input.MouseEvent; import org.jrebirth.af.api.exception.CoreException; import org.jrebirth.af.core.ui.AbstractController; import org.jrebirth.af.core.ui.adapter.DefaultMouseAdapter; import org.jrebirth.af.core.wave.Builders; import org.jrebirth.af.sample.command.SampleCommand; import org.jrebirth.af.sample.command.SamplePoolCommand; import org.jrebirth.af.sample.command.SampleUICommand; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * The class <strong>SampleController</strong>. * * @author */ public final class SampleController extends AbstractController<SampleModel, SampleView> { /** The class logger. */ private static final Logger LOGGER = LoggerFactory.getLogger(SampleController. class ); /** * Default Constructor. * * @param view the view to control * * @throws CoreException if an error occurred while creating event handlers */ public SampleController( final SampleView view) throws CoreException { super (view); } /** * {@inheritDoc} */ @Override protected void initEventAdapters() throws CoreException { // Manage Ui Command Button linkCommand(getView().getUiCommand(), MouseEvent.MOUSE_CLICKED, SampleUICommand. class ); // Use the inner class addAdapter( new SampleMouseAdapter()); } /** * {@inheritDoc} */ @Override protected void initEventHandlers() throws CoreException { // Listen events // Manage Pooled Command Button getView().getPooledCommand().setOnMouseClicked(getHandler(MouseEvent.MOUSE_CLICKED)); } /** * Manage Mouse click of widget that have annotation. * * @param event the mouse event */ void onMouseClicked( final MouseEvent event) { LOGGER.debug( "MouseClicked => Call Sample Command" ); // Manage Default Command Button getModel().getCommand(SampleCommand. class ).run(); } /** * The class <strong>SampleMouseAdapter</strong>. */ private class SampleMouseAdapter extends DefaultMouseAdapter<SampleController> { @Override public void mouseClicked( final MouseEvent mouseEvent) { super .mouseClicked(mouseEvent); LOGGER.debug( "MouseClicked => Call Sample Pool Command" ); getModel().sendWave(Builders.callCommand(SamplePoolCommand. class )); } } } |