JRebirth Application Framework provides a custom 'CardLayout' 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 dynamycally 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 (StakPane, 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() {
UniqueKey<? extends Model> modelKey;
switch (this) {
default:
case ViewEmbeddedFxml:
modelKey = Key.create(EmbeddedModel.class);
break;
case StandaloneFxml:
modelKey = Key.create(StandaloneModel.class);
break;
case HybridFxml:
modelKey = Key.create(HybridModel.class, FXMLModel.KEYPART_FXML_PREFIX + "org.jrebirth.af.showcase.fxml.ui.hybrid.Hybrid");
break;
case IncludedFxml:
modelKey = Key.create(IncludedModel.class, new LoremIpsum());
break;
}
return modelKey;
}
}
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));
}
}
Let me know if you have any question