Skip to content

09. Shell

FrankHossfeld edited this page Jul 12, 2023 · 6 revisions

Shell

The shell is the root view of the application. It divides the screen in several areas, which can be used to add components. A Nalu application supports more than one shell. The definition, which shell is currently to use, the shell is added to the route. The first part of the route is always a shell.

A route in Nalu will look like that:

/loginShell/login

This route tells Nalu to use a shell with the name loginShell and a controller that uses loginShell/login as route. In case the current shell is not named loginShell, Nalu will remove the current shell and add the new shell to the viewport.

If there is a need to attach handlers to DOM elements, you can use the bind-method of the shell to add the handlers to the DOM.

Depending on the plugin you are using, you have to tell Nalu the extension points in different ways.

Defining a shell in Nalu

To define a shell in Nalu, you have to use the @Shell-annotation with your shell class and extend the AbstractShell-class. The annotation takes one attribute. That's the name of the shell. This name will be used to identify the shell of the route.

Example of a shell class:

@Shell("applicationShell")
public class MyApplicationShell
    extends AbstractShell<MyApplicationContext> {
  
  ...
  
}

The code above will define a shell which can be referenced by a route using 'applicationShell'.

To tell Nalu to use this shell, set the first part of the route to 'applicationShell':

/applicationShell/detail

Nalu will look for a shell defined with the name 'applicationShell' and add it to the viewport (in case it is not the current shell). In case that a shell is already added to the viewport, Nalu will remove the current shell, call the onDetach-method and add the new shell to the viewport.

Depending on the Nalu processor plugin that is used, a shell implementation may look different. See the next paragraphs for more information.

Attach shell

You need to override the attachShell-method with the code needed to add the shell to the viewport.

Detach shell

You need to override the detachShell-method with the code needed to remove the shell from the viewport.

Nalu Elemental2 & Elemento plugin

In case you are working with the nalu-plugin-elemental2 or nalu-plugin-elemento, Nalu uses the id to look for nodes inside the DOM.

An implementation using the nalu-plugin-elemental2 (using Elemento) looks like this:

@Shell("applicationShell")
public class ApplicationShell
    extends AbstractShell<MyApplicationContext> {
  
  private HTMLDivElement shell;

  public Shell() {
  }

  @Override
  public void attachShell() {
    document.body.appendChild(this.render());
  }

  @Override
  public void detachShell() {
    document.body.removeChild(this.shell);
  }

  private HTMLElement render() {
    document.body.style.margin = CSSProperties.MarginUnionType.of(0);
    
    this.shell = div().css("shell")
                .add(createNorth())
                .add(createSouth())
                .add(div().css("navigation")
                          .attr(Nalu.NALU_ID_ATTRIBUTE,
                                "navigation")
                          .get())
                .add(div().css("shellContent")
                          .attr(Nalu.NALU_ID_ATTRIBUTE,
                                "content")
                          .get())
                .get();

    return this.shell;
  }

  private Element createNorth() {
    return header().css("header")
                   .attr(Nalu.NALU_ID_ATTRIBUTE,
                         "header")
                   .get();
  }

  private Element createSouth() {
    return footer().css("footer")
                   .attr(Nalu.NALU_ID_ATTRIBUTE,
                         "footer")
                   .get();
  }
}

The shell of the example will divide the viewport in several slots with identifiers like:

  • header
  • footer
  • navigation
  • content.

This identifiers can be now used as selectors inside the @Controller-annotation to define the slot the component of the controller gets visible.

Nalu GWT plugin

In case you are working with the nalu-plugin-gwt, Nalu uses the add-method to add a widget to a panel. The code for the add is generated via a processor. Therefore, it is necessary to have package protected instance variables which are annotated with @Selector("[name of the selector]"). The processor will generate the code to attach a widget.

Important: you need to create a provider to make your selectors available for Nalu.

To do so, implement these two lines inside the bind-method:

    IsSelectorProvider<Shell> provider = new ShellSelectorProviderImpl();
    provider.initialize(this);

An implementation using the nalu-plugin-gwt looks like this:

@Shell("applicationShell")
public class ApplicationShell
  extends AbstractShell<MyApplicationContext> {

  private SimpleLayoutPanel headerWidget;
  private ResizeLayoutPanel footerWidget;
  private SimpleLayoutPanel navigationWidget;
  private SimpleLayoutPanel contentWidget;
  private ResizeLayoutPanel shell;
  private ApplicationCss    style;

  public Shell() {
    super();
  }

  @Override
  public void attachShell() {
    RootLayoutPanel.get()
                   .add(this.render());
  }

  @Override
  public void detachShell() {
    this.shell.removeFromParent();
  }

  private Widget render() {
    this.style = ApplicationStyleFactory.get()
                                        .getStyle();

    shell = new ResizeLayoutPanel();
    shell.setSize("100%",
                  "100%");

    DockLayoutPanel panel = new DockLayoutPanel(Style.Unit.PX);
    panel.setSize("100%",
                  "100%");
    shell.add(panel);

    this.headerWidget = createNorth();
    panel.addNorth(this.headerWidget,
                   128);

    this.footerWidget = createSouth();
    panel.addSouth(this.footerWidget,
                   42);

    this.navigationWidget = createNavigation();
    panel.addWest(this.navigationWidget,
                  212);

    this.contentWidget = createContent();
    panel.add(this.contentWidget);

    return panel;
  }

  private SimpleLayoutPanel createNorth() {
    SimpleLayoutPanel panel = new SimpleLayoutPanel();
    panel.addStyleName(style.headerPanel());
    panel.getElement()
         .setId("header");
    return panel;
  }

  private ResizeLayoutPanel createSouth() {
    ResizeLayoutPanel footerPanel = new ResizeLayoutPanel();
    footerPanel.getElement()
               .setId("footer");
    return footerPanel;
  }

  private SimpleLayoutPanel createNavigation() {
    SimpleLayoutPanel panel = new SimpleLayoutPanel();
    panel.addStyleName(style.navigationPanel());
    panel.getElement()
         .setId("navigation");
    return panel;
  }

  private SimpleLayoutPanel createContent() {
    SimpleLayoutPanel panel = new SimpleLayoutPanel();
    panel.getElement()
         .setId("content");
    return panel;
  }

  @Override
  public void bind(ShellLoader loader)
    throws RoutingInterceptionException {
    IsSelectorProvider<Shell> provider = new ShellSelectorProviderImpl();
    provider.initialize(this);
    DomGlobal.window.alert("Stop inside bind-method of Login-Shell");
    // call loader.continueLoading() to tell Nalu to continue
    loader.continueLoading();
  }

  @Selector("header")
  public void setHeader(Widget widget) {
    this.headerWidget.clear();
    this.headerWidget.add(widget);
  }

  @Selector("footer")
  public void setFooter(Widget widget) {
    this.footerWidget.clear();
    this.footerWidget.add(widget);
  }

  @Selector("navigation")
  public void setNavigation(Widget widget) {
    this.navigationWidget.clear();
    this.navigationWidget.add(widget);
  }

  @Selector("content")
  public void setContent(Widget widget) {
    this.contentWidget.clear();
    this.contentWidget.add(widget);
  }
}

The shell of the example will divide the viewport in several slots with identifiers like:

  • header
  • footer
  • navigation
  • content.

This identifiers can be now used as selectors inside the @Controller-annotation to define the slot the component of the controller gets visible.

Async bind Method (since v1.3.0)

Nalu will call the bind-method before the shell is added to the viewport. This is an internal method, that can be overwritten. This is a good place to do some preparing actions. The method is asynchronous. This makes it possible to do a server call before Nalu will continue. To tell Nalu to continue, call loader.continueLoading(). In case you want to interrupt the loading, just do not call loader.continueLoading() and use the router to route to another place.

Example of a bind method inside the shell.

  @Override
  public void bind(ShellLoader loader)
    throws RoutingInterceptionException {

    // call loader.continueLoading() to tell Nalu to continue
    loader.continueLoading();

  }

Post Attach

Nalu will call the onAttachedComponent-method after a new component is attached. If you need to do something after a new component is attached to the dom, this is good place to do so. F.e.: in case you are using GXT and want to do a forceLayout after a component is attached, use the following code:

  @Override
  public void onAttachedComponent() {
    this.shell.forceLayout();
  }