How to Use ‘CardLayout’ like Panel with JRebirth StackModel

JRebirth Application Framework provides a custom ‘CardLayout’ as a JRebirth component by using its dedicated pattern wB-CSMvc.

The StackModel class will do the job (provided by org.jrebirth.af:component artifact), you can find 2 usages here and here.

Each ‘card’ model can be called using a enum|modelKey identifier, and each stack has an unique name.

The first sample is used for the JRebirth Demo Application, it’s a pretty simple application that will allow to display other JRebirth showcase applications dynamically loaded as JRebirth module (from a separate and independent jar).

public final class JRebirthDemo extends DefaultApplication<StackPane> {

    public static void main(final String... args) {
        Application.launch(JRebirthDemo.class, args);
    }

    @Override
    public Class<? extends Model> firstModelClass() {
        return MainModel.class;
    }

    @Override
    protected String applicationTitle() {
        return "JRebirth Demo Application";
    }

    @Override
    protected void customizeScene(final Scene scene) {
        super.customizeScene(scene);

        addCSS(scene, DemoStyles.DEFAULT);
        addCSS(scene, WorkbenchStyles.DEFAULT);
    }

    @Override
    protected void customizeStage(final Stage stage) {
        // Center the stage
        stage.centerOnScreen();
    }

    @Override
    protected List<? extends ResourceItem<?, ?, ?>> getResourceToPreload() {
        return Collections.emptyList();
    }
}

This application will load its first model (MainModel) and put its root node into the scene root node (StackPane, automatically built).The MainModel will list all application’s sub modules to add a button entries into its left menu, and a StackModel that will display each module content. The StackModel is loaded using special annotation using its unique String key.

public final class MainModel extends DefaultModel<MainModel, MainView> {

    private final List<ModuleModel> modules = new ArrayList<>();

    @Link("DemoStack")
    private StackModel stackModel;

    @Override
    protected void initModel() {
        for (final ModuleModel mm : getModels(ModuleModel.class)) {
            this.modules.add(mm);
        }
    }

    @Override
    protected void showView() {
        view().node().setCenter(this.stackModel.node());
    }

    @Override
    protected void hideView() {
        // Nothing to do yet
    }

    List<ModuleModel> getModules() {
        return this.modules;
    }
}

The MainView will be in charge to create the module menu:

public final class MainView extends DefaultView<MainModel, BorderPane, MainController> {

    private final List<Button> buttonList = new ArrayList<>();

    public MainView(final MainModel model) throws CoreException {
        super(model);
    }

    @Override
    protected void initView() {

        node().setPrefSize(800, 600);

        node().setLeft(createMenu());

    }

    @Override
    public void start() {
        this.buttonList.stream().findFirst().ifPresent(button -> button.fire());
    }

    private Node createMenu() {
        final VBox box = new VBox();

        for (final ModuleModel mm : model().getModules()) {
            final Node n = createModuleButton(mm);
            VBox.setMargin(n, new Insets(4, 4, 4, 4));
            box.getChildren().add(n);
        }
        return box;
    }

    private Node createModuleButton(final ModuleModel mm) {
        final Button b = new Button(mm.moduleName());
        b.getStyleClass().add("menuButton");
        b.setPrefSize(100, 50);
        b.setOnAction(controller()::onButtonFired);
        b.setUserData(Key.create(mm.getClass()));
        this.buttonList.add(b);
        return b;
    }
}

And the MainController will load the Module content when any menu button is triggered:

public final class MainController extends DefaultController<MainModel, MainView> implements ActionAdapter {

    public MainController(final MainView view) throws CoreException {
        super(view);
    }

    public void onButtonFired(final ActionEvent event) {
        final Button b = (Button) event.getSource();
        final UniqueKey<? extends Model> data = (UniqueKey<? extends Model>) b.getUserData();

        model().sendWave(StackWaves.SHOW_PAGE_MODEL,
                         WBuilder.waveData(StackWaves.PAGE_MODEL_KEY, data),
                         WBuilder.waveData(StackWaves.STACK_NAME, "DemoStack"));
    }
}

The second example will load the StackModel as an innerComponent and each card will be identified by an enum entry (stored into FXMLPage), let’s see FXMLShowCaseModel :

final InnerComponent<StackModel> stack = CBuilder.innerComponent(StackModel.class, FXMLPage.class);
    this.stackModel = findInnerComponent(stack);

The enumeration that link enum entry with Model:

public enum FXMLPage implements PageEnum {

    StandaloneFxml,
    IncludedFxml,
    ViewEmbeddedFxml,
    HybridFxml;

    @Override
    public UniqueKey<? extends Model> getModelKey() {
        switch (this) {
            default:
            case ViewEmbeddedFxml:
                return Key.create(EmbeddedModel.class);
            case StandaloneFxml:
                return Key.create(StandaloneModel.class);
            case HybridFxml:
                return Key.create(HybridModel.class, FXMLModel.KEYPART_FXML_PREFIX + "org.jrebirth.af.showcase.fxml.ui.hybrid.Hybrid");
            case IncludedFxml:
                return Key.create(IncludedModel.class, new LoremIpsum());
        }
    }

}

As the card list is known, toolbar item are statically created into FXMLShowCaseView and event handling is also staically defined into FXMLShowCaseController using another technique:

public final class FXMLShowCaseController extends DefaultController<FXMLShowCaseModel, FXMLShowCaseView> {

    private static final Logger LOGGER = LoggerFactory.getLogger(FXMLShowCaseController.class);

    public FXMLShowCaseController(final FXMLShowCaseView view) throws CoreException {
        super(view);
    }

    @Override
    protected void initEventAdapters() throws CoreException {

        // WaveData<Class<? extends PageEnum>> stackName = Builders.waveData(StackWaves.STACK_PAGES, FXMLShowCaseModel.STACK_PAGES);

        // Manage Ui Command Button
        linkWave(view().getShowIncluded(), ActionEvent.ACTION, StackWaves.SHOW_PAGE_ENUM,
                 WBuilder.waveData(StackWaves.PAGE_ENUM, FXMLPage.IncludedFxml));

        linkWave(view().getShowEmbedded(), ActionEvent.ACTION, StackWaves.SHOW_PAGE_ENUM,
                 WBuilder.waveData(StackWaves.PAGE_ENUM, FXMLPage.ViewEmbeddedFxml));

        linkWave(view().getShowStandalone(), ActionEvent.ACTION, StackWaves.SHOW_PAGE_ENUM,
                 WBuilder.waveData(StackWaves.PAGE_ENUM, FXMLPage.StandaloneFxml));

        linkWave(view().getShowHybrid(), ActionEvent.ACTION, StackWaves.SHOW_PAGE_ENUM,
                 WBuilder.waveData(StackWaves.PAGE_ENUM, FXMLPage.HybridFxml));

    }
}

Here you have the final application that allows to open each module dependency into the Stack using a Toggle button

 

Leave a Reply