/ DataFX / A quick overview to the DataFX MVC and Flow API

A quick overview to the DataFX MVC and Flow API

Hendrik on 2015/02/13 - 00:56 in DataFX, Desktop Application Framework (JSR 377), JavaFX

This is a short overview of the controller and flow API of DataFX that I have written for the JSR377 mailing list. If you want to discuss the architecture in general please add a reply to the mailing list.

DataFX provides a MVC approach that adds a lot of API sugar to the FXML API of JavaFX. As a first example you can find a simple controller here:

DataFX Controllers supports a lot of standard annotations like @PostConstruct. In addition the @ViewNode and @ViewController Annotation can be used to provide a link to the view. @ViewNode does mainly the same as @FXML but provides additional functionalities. Actions can be defined by an unique id. Therefore you can use Annotations to bind controls to actions. If you want a action to be called in a background thread you only need to add the @Async Annotation to the method.

Next to this there are some special annotations to inject DataFX related Objects like an exception handler or the DataFX concurrency toolkit. Here is an example:

On top of the MVC API is the Flow API. By using this API you can define flows in your application. By doing so you can define links between views or actions that will open a specific view. A Flow can easily defined by using the controller classes:

When using a flow you can define actions as global actions for the flow or actions that are view specific.

Next to this the Flow API adds a lot of new annotations. By adding the @BackAction annotation to a node like a button the flow will navigate back to the last view once the button is clicked. Instead of defining the links in the flow definition you can use the @LinkAction(NextViewController.class) annotation.

In addition the Flow API provides CDI by using @Inject. To do so 4 different scopes are defined:

  • application scope (singleton)
  • flow scope (same instance will be injected in every controller / view / model of the flow)
  • view scope (same instance will be injected in the controller / model of the view)
  • dependend (always inject a new instance)

In addition DataFX provides a event system. This can be used to handle events between 2 separated flows, for example. Each event type is defined by a unique id. The following code snippet defines two controllers that can be used in complete different parts of the application and don’t know each other. By using the event system the first controller can send events to the second one by clicking the button:


If you want the async events you only need to add the @Async annotation to the producer or / and receiver. By doing so you can create events on a background thread that will be received on the platform thread.

DataFX provides a PlugIn API that can be used to define additional components / plugins that will be managed by DataFX. Therefore you can define two types of Annotations: A injection annotation that can be used to inject specific objects or a handler annotation that can be use to do some stuff with objects. Here are two examples:

Example A
DataFX provides a plugin for feature driven development. By doing so you can define nodes in your controller like this:

Now you can use the togglz API to define feature toggles and change them at runtime. If you for example disable the PLAY_FEATURE the playButton will become hidden. This is en example for a plugin that provides handler annotations.

Example B
DataFX provides a Plugin for remote EJBs. By using the plugin you can inject a Remote-EJB-Proxy in your controller:


In this case the EJB Wrapper / Proxy will be injected by using the custom / plugin annotation

If you want to now more you should have a look at the DataFX tutorials or ask me :)

15 POST COMMENT

Send Us A Message Here

Your email address will not be published. Required fields are marked *

15 Comments
  • 2015/02/13

    Hi Hendrik,

    1. My primary concern is the heavy use of Strings, which means lack of type safety. The compiler does not prevent me from misspelling those.

    2. In the flow definition, what is “next”? Is it an ID or label of a button? I would like to define a flow that transitions to the next view when a certain condition is met in the current view, for example, a boolean ObservableValue on the current view becomes true. Is that possible?

    3. The “event system” looks like a global (!!!) event bus. I have argued why I think event bus is a bad idea in this StackOverflow answer, point 3, which basically says that in a large application you will have no idea who sends messages to who.

    Tomas Mikula
    Reply
    • 2015/02/13

      Hi Tomas,

      thanks for your feedback :)

      1. In general I’m with you. Any place where Strings aren’t needed you can use Java Objects. But when developing a modularized architecture there are some problems. Maybe you have a general help dialog in package A and your business flow in package B. B doesn’t know A. In this case it makes sense to use an id to define the views and link from one to the other one. If they know each other you can use the class (linkTo(XYController.class)). About Action ID: Here DataFX uses Strings, too. This is done because you can have global actions or actions that are defined in a flow. In a view controller you don’t know about this and therefore IDs are used. Ok, in this case you can say: If the view doesn’t know anything about the flow or action, why should it knows the ID? This is done to create a lightweight framework with that views, flows and applications can be created fast by creating code that is easy to read.

      2. “next” is an action id. Let’s say in View1 you have defined a button that acts as the link and by clicking the link the flow should navigate to the next view / controller. This can be done by using the @ActionTrigger annotation:

      @ActionTrigger(“next”)
      Button navButton;

      If you don’t like to use strings here you can define the link directly by using the controller class:

      @LinkAction(NextController.class)
      Button navButton;

      Sometimes you need the first way because you don’t want to have a dependency between view 1 and view2.

      3. Yes, it’s a global one. I will read your post later today before I can give a more detailed answer :)

      Reply
      • 2015/02/13

        I am all for modular architecture and independent components that don’t know about each other. But the entity that is wiring two components knows about both of them, so this is where they can be connected in a type-safe manner. This is how I imagine it could be done, replacing an action ID for a ReactFX-like EventStream:

        Flow flow = new Flow(WizardView1Controller.class).
        withLink(WizardView1Controller.class, view1 -> EventStreams.eventsOf(view1.getNextButton(), ActionEvent.ACTION), WizardView2Controller.class);

        The second argument to withLink was changed to a function that, given an instance of WizardView1Controller, returns an event stream, i.e. the type of the second argument is Function<T, EventStream<?>>, where T is the type of the first argument, here WizardView1Controller.

        This seems somewhat verbose compared to just saying “next”, but is very flexible – it does not require any annotations on the WizardView1Controller class, so can be used event with views that were not designed to be used in a DataFX flow. If the view already exposed a stream of action events like this:

        class WizardView2Controller {
        public EventStream nextActions() {
        // implementation just to illustrate how easy it is actully to implement
        return EventStreams.eventsOf(nextBtn, ActionEvent.ACTION);
        }
        }

        then the above could be rewritten like this:

        Flow flow = new Flow(WizardView1Controller.class).
        withLink(WizardView1Controller.class, WizardView1Controller::nextActions, WizardView2Controller.class);

        I’m not saying that the Flow API should use ReactFX (althought I would love it! ;)), I’m just trying to argue that it should be made more type safe (let the compiler find the bugs!).

        Tomas Mikula
        Reply
  • Pingback:JavaFX links of the week, February 16 // JavaFX News, Demos and Insight // FX Experience

  • 2015/02/25

    Hi Hendrik,
    Thank you for creating this useful library. I am new to DataFX and trying to use the feature toggling. From this blog post, I have no clue how to get started with this library. And more over your previous series of tutorial on datafx are invalid and the library has been updated with lot of new features and new annotations and not in sync with your recent blog post. which is more confusing to readers. I would request you to associate your blog post with one of the version of DataFX. Or release notes like .

    Ganesh
    Reply
  • 2015/02/25

    I am getting a exception like this..there is no META-INF/datafx.xml file


    Caused by: java.lang.RuntimeException: Can't generate Feature Manager
    at io.datafx.featuretoggle.FeatureHandler.createManagerFromConfig(FeatureHandler.java:41)
    at io.datafx.featuretoggle.FeatureHandler.getManager(FeatureHandler.java:47)
    at io.datafx.featuretoggle.FeatureHandler.createFeatureProperty(FeatureHandler.java:53)
    at io.datafx.featuretoggle.FeatureHandler.hideByFeature(FeatureHandler.java:57)
    at io.datafx.featuretoggle.HideByFeatureResourceConsumer.consumeResource(HideByFeatureResourceConsumer.java:11)
    at io.datafx.featuretoggle.HideByFeatureResourceConsumer.consumeResource(HideByFeatureResourceConsumer.java:7)
    at io.datafx.controller.context.ContextResolver.injectResources(ContextResolver.java:70)
    at io.datafx.controller.ViewFactory.createByController(ViewFactory.java:185)
    ... 23 more
    Caused by: java.lang.RuntimeException: Can't load DataFX configuration. Please check META-INF/datafx.xml
    at io.datafx.core.DataFXConfiguration.readDataFXConfiguration(DataFXConfiguration.java:91)
    at io.datafx.core.DataFXConfiguration.getConfigurationDocument(DataFXConfiguration.java:79)
    at io.datafx.core.DataFXConfiguration.getElements(DataFXConfiguration.java:68)
    at io.datafx.featuretoggle.FeatureHandler.createManagerFromConfig(FeatureHandler.java:28)
    ... 30 more
    Caused by: java.lang.IllegalArgumentException: InputStream cannot be null
    at javax.xml.parsers.DocumentBuilder.parse(DocumentBuilder.java:117)
    at io.datafx.core.DataFXConfiguration.readDataFXConfiguration(DataFXConfiguration.java:89)

    Ganesh
    Reply
  • Pingback:JavaFX links of the week, February 16 // JavaFX News, Demos and Insight // FX Experience | État 2 veille

  • 2015/06/11

    I use the a @EventProducer but my dude is how can manage the event if the consumer is the same controller but two diferents flows

    Cristhian Ivan Castañeda Rivera
    Reply
    • 2015/06/11

      Hi,
      this should work. If you found a bug can you please create a small example and post an issue at bitbucket?

      Reply
  • 2015/06/11

    my question was that bad , I meant the @EventProducer is a one controller but @OnEvent are two flows but whith the same controller i just only want one of those two consume the event

    this is my producer
    /**************************************************************************/
    @EventProducer(value = “return-array”)
    private ObservableList sendarray() {
    ObservableList staticSales = FXCollections.observableArrayList();
    staticSales.clear();
    for (ArticlesEntity entity : tblArticles.getSelectionModel().getSelectedItems()) {
    SalesDTO dto = new SalesDTO();
    dto.setNameArticle(entity.getName());
    dto.setAmount(1);
    dto.setCantity(entity.getBaseCost());
    dto.setArticlesEntity(entity);
    String s;
    if (entity.isPackage()) {
    s = “Es paquete con ” + entity.getUnitsPackage() + “Unidades”;
    } else {
    s = “No es un paquete”;
    }
    dto.setIspackage(s);
    staticSales.add(dto);
    }
    stage = (Stage) tblArticles.getScene().getWindow();
    stage.close();
    return staticSales;
    }
    /***********************************************************************/
    this is my on event
    /**********************************************************************/
    @OnEvent(value = “return-array”)
    private void putarray(Event<ObservableList> e){
    for (SalesDTO DTO : e.getContent()) {
    boolean b = false;
    SalesDTO d = null;
    SalesDTO art;
    Iterator itr = salesDTOs.iterator();
    while (itr.hasNext()) {
    art = (SalesDTO) itr.next();
    if (art.getArticlesEntity().getBarcode().equalsIgnoreCase(DTO.getArticlesEntity().getBarcode())) {
    d = art;
    itr.remove();
    d.setAmount(d.getAmount() + 1);
    d.setCantity(DTO.getArticlesEntity().getBaseCost() + d.getCantity());
    salesDTOs.addAll(d);
    CopyOnWriteArrayList sa = new CopyOnWriteArrayList(salesDTOs);
    tblsales.getItems().clear();
    salesDTOs.addAll(sa);
    tblsales.setItems(salesDTOs);
    calculeTotal(salesDTOs);
    b = true;
    break;
    } else {
    b = false;
    }
    }
    if (b) {
    b = false;
    } else {
    salesDTO = new SalesDTO();

    salesDTO.setNameArticle(DTO.getArticlesEntity().getName());
    salesDTO.setCantity(DTO.getArticlesEntity().getBaseCost());
    String s;
    if (DTO.getArticlesEntity().isPackage()) {
    s = “Es packete con ” + DTO.getArticlesEntity().getUnitsPackage() + ” unidades”;
    } else {
    s = “No es un packete”;
    }
    salesDTO.setIspackage(s);
    salesDTO.setAmount(1);
    salesDTO.setArticlesEntity(DTO.getArticlesEntity());
    salesDTOs.addAll(salesDTO);
    calculeTotal(salesDTOs);
    b = false;
    }

    }

    }
    /************************************************************************/
    the on event will consume twice i just want one consume can you help me please ???

    Cristhian Ivan Castañeda Rivera
    Reply
  • 2015/08/12

    Hi.

    I am trying out some annotations but it seems to be a mixup somewhere.
    I thing @FXMLController replaces @ViewController since a while back maybe. This is a one of the newest tutorial it seems but ViewController is still used.

    Also I have found the following versions of the flow api on maven central. 8.0, 8.0.1 and 8.0.7
    8.0.1 is missing FXMLController.
    8.0.7 has FXMLController but @LinkAction, @ActionTrigger and @ActionMethod does not work it seems.

    The annotations mentioned above seems to work fine with version 8.0.

    Would be nice if you had an answer to this issues Hendrik.

    -Ronny

    Ronny
    Reply
    • 2015/08/17

      Yes, there are some version problems with the tutorials and the DataFX versions. 8.0.7 is a broken release (wrong version umber, should be 8.0b7) and you should use 8.0.1
      I hope to find some time before JavaOne to create a new release

      Reply
      • 2015/08/17

        Thanks. I will go for v8.0.1 and @ViewController :-)

        Ronny
        Reply
  • Pingback:JavaFX links of the week, February 16 // JavaFX News, Demos and Insight // FX Experience | État de veille

  • Pingback:JavaFX links of the week, February 16 // JavaFX News, Demos and Insight // FX Experience – My Worlds