Fork me on GitHub

User Interface Area

Use a custom MVC pattern with a lot of convenient tricks

UI Roles

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:

Models

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

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

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).

About Generics

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.

Main Usage

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:

  • M - Only Model: rely on SimpleModel implementation
  • MV - Model with a View: Controller is omitted
  • MVC - Model, View, Controller: Basic Implementation
  • FMFFC - FxmlModel , Fxml, FxmlController: View and Controller are respectively replaced by Fxml and FxmlController
  • MVCFFC - FxmlModel, View, Controller, Fxml, FxmlController: Basic + Fxml files

Model Overview

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
    }
  
}

Views Overview

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.

View’s initialization

The View’s initialization code perform several operations:

  1. Link the model (strong reference)
  2. Create the root Node object according to the first generic type that extends Node.class
  3. Create the Controller according to the first generic type that extends Controller.class

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");
  
        node().setCenter(
                                LabelBuilder.create()
                                            .text("JRebirth Sample")
                                            .build()
                     );
  
        node().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;
    }
  
}

Annotation Event Handler

Controllers

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.WBuilder;
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(view().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
        view().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
        model().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");
  
            model().sendWave(WBuilder.callCommand(SamplePoolCommand.class));
        }
  
    }
  
}

Adapters & Handlers