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