Application Framework

ApplicationLayout

When using linkki Application Framework, all views should be displayed in a customized ApplicationLayout which provides the application frame. Therefore, the first step to use application framework is to create a subclass of ApplicationLayout.

The main purpose of this class is to hold the custom implementation of ApplicationConfig in which the application frame is configured. This custom implementation can be passed to the super constructor. The implementation of ApplicationLayout itself should have a default constructor which makes it usable as a RouterLayout.

Example ApplicationLayout
@Theme(Lumo.class)
public class PlaygroundAppLayout extends ApplicationLayout {

    private static final long serialVersionUID = -5604950024464910529L;

    public PlaygroundAppLayout() {
        super(new PlaygroundApplicationConfig());
    }

}

The ApplicationLayout that surrounds the view with an ApplicationHeader and an optional ApplicationFooter.

The header is designed to have multiple menu actions on the left and may have some specific items like help menu or preferences on the right side.

The footer is hidden by default and can simply contain some application info.

Between ApplicationHeader and ApplicationFooter is the main area that displays the current view.

ApplicationMenuItemDefinition

MenuItems can be added to the ApplicationHeader using ApplicationMenuItemDefinition.

For creation, ApplicationMenuItemDefinition provides following constructors:

  • ApplicationMenuItemDefinition(String, Handler): Name of the menu and a Handler that is executed on click.

  • ApplicationMenuItemDefinition(String, List<ApplicationMenuItemDefinition>): Name of the menu and a list of ApplicationMenuItemDefinitions as sub-menu items. Sub-menus are added in order as defined in the sub-menu list.

  • ApplicationMenuItemDefinition(String, String): Name of the menu and a URL as String to navigate to on click. This method handles both external and internal links.

  • ApplicationMenuItemDefinition(String, Class<? extends Component>): Name of the menu and a Class to navigate to. The given class should be a route component.

More complex menu items can be implemented by creating menu item definitions directly instead of inheriting. If the implementation needs to be customizable, it can be easily done by composing ApplicationMenuItems. The following example uses a factory class to create the menu. A menu item contains one sub-menu item with caption "New". If only one sub-sub-menu item is given, the sub-menu-item shows a notification on click (caption → New). If multiple sub-sub-menu items are given, those are shown in the sub-menu item (caption → New → sub-sub-menu items). This can be used in ApplicationConfig#getMenuItemDefinitions()

CustomMenuItemDefinitionCreator
public class CustomMenuItemDefinitionCreator {

    public static ApplicationMenuItemDefinition createMenuItem(String name, List<MySubSubMenuItem> subSubMenuItems) {
        return new ApplicationMenuItemDefinition(name, Collections.singletonList(createSubMenuItem(subSubMenuItems)));
    }

    private static ApplicationMenuItemDefinition createSubMenuItem(List<MySubSubMenuItem> subSubMenuItems) {
        String subMenuName = "New";
        if (subSubMenuItems.size() > 1) {
            return new ApplicationMenuItemDefinition(subMenuName,
                    subSubMenuItems.stream()
                            .map(i -> new ApplicationMenuItemDefinition(i.getName(), () -> i.getCommand().apply()))
                            .collect(Collectors.toList()));
        } else if (subSubMenuItems.size() == 1) {
            MySubSubMenuItem onlyItem = subSubMenuItems.get(0);
            return new ApplicationMenuItemDefinition(onlyItem.getName(), onlyItem.getCommand());
        } else {
            return new ApplicationMenuItemDefinition(subMenuName, Handler.NOP_HANDLER);
        }
    }

    public static class MySubSubMenuItem {
        private String name;

        public MySubSubMenuItem(String name) {
            this.name = name;
        }

        public String getName() {
            return name;
        }

        public Handler getCommand() {
            return () -> Notification.show(name);
        }
    }
}

To add a MenuItem when to your own ApplicationMenu or SubMenu the following methods can be used:

  • createItem(ApplicationMenu): Creates and adds a MenuItem to the given ApplicationMenu.

  • createItem(SubMenu): Creates and adds a MenuItem to the given SubMenu.

ApplicationMenu
ApplicationMenuItemDefinition startMenuItemDefinition = new ApplicationMenuItemDefinition("Start", () -> goToStart());
ApplicationMenu menuBar = new ApplicationMenu();

startMenuItemDefinition.createItem(menuBar);
SubMenu
ApplicationMenuItemDefinition itemDefinition = new ApplicationMenuItemDefinition("Start", () -> goToStart());
-- new MenuBar to create item for sub-menu
MenuBar menuBar = new MenuBar();
SubMenu subMenu = menuBar.addItem("item").getSubMenu();

itemDefinition.createItem(subMenu);

Application Configuration

The main aspects of the application are configured using the ApplicationConfig. This interface needs to be implemented once in every linkki application and provided to the ApplicationLayout.

ApplicationConfig refers to an ApplicationInfo which defines basic information about the application such as name and copyright. Furthermore, parts of ApplicationLayout is configured by the ApplicationConfig, such as the header, footer, as well as converters that should be used to convert values between UI and the underlying model.

Components

The linkki application framework contains the following components:

LinkkiTabLayout

The LinkkiTabLayout is a UI component that gives access to several different views.

sidebar layout

On the left there is a vertical bar containing icons (buttons) for every sheet. The tooltip of the button displays the name of the corresponding sheet. The content of the selected sheet is displayed to the right of the bar.

In order to create a sidebar instantiate the LinkkiTabLayout and add LinkkiTabSheets that can be built using a builder. Every sheet requires an icon, a name and a content supplier (Supplier<Component>). The supplier is called when the sheet is selected for the first time (lazy initialization). This approach only creates the content if it is requested by the user. It also spreads out the component creation, avoiding long loading times at the start.

Additionally you can supply a UiUpdateObserver that is triggered every time the sheet is selected, that means it gets visible. Use this observer to update your binding context in case of changes to the underlying model while the sheet was not selected. Our sample application contains a sidebar layout with two sheets that highlights the utility of that approach. The second sheet displays a list of reports which are created on the first sheet. Thus the second sheet needs to be updated every time it is selected because the underlying data might have changed in the meantime.

        addTabSheets(LinkkiTabSheet.builder("CreateReport")
                .caption(VaadinIcon.STAR_HALF_LEFT_O.create())
                .content(this::createReportLayout)
                .build(),
                     LinkkiTabSheet.builder("ReportList")
                             .caption(VaadinIcon.FILE_O.create())
                             .content(this::createReportListLayout)
                             .build());

Headline

For every sheet it is useful to have a headline that describes the current content. It natively has a headline caption and could be extended by subclasses.

create report simple

To use a Headline simply instantiate and add the component to your content.

If you want the Headline’s title to be updated dynamically, you can also bind it to a PMO. To do so, create a PMO containing a corresponding getter method for Headline#HEADER_TITLE:

public class HeadlinePmo {

    private List<Report> reports;

    public HeadlinePmo(List<Report> reports) {
        this.reports = reports;
    }

    public String getHeaderTitle() {
        return "Report List - Existing reports: " + reports.size();
    }
}

Then bind it with the headline:

        new Binder(headline, headlinePmo).setupBindings(getBindingContext());