Krail
master

Tutorial

  • Introduction to the Tutorial
  • Getting Started
  • Page Navigation
    • Defining a Page
    • Introducing I18N
      • Create an I18N Annotation
    • Add a Page - direct method
      • Defining the I18NKeys
    • Using the Pages
    • View the Pages
    • Add a Page - Annotation method
    • Choosing the Method
    • Moving a Set of Pages
    • Navigation
      • Add some public pages
      • Getting the Navigator
      • Adding some components
      • Navigating with Parameters
        • Receiving parameters
        • Sending parameters
      • Excluding a page from Navigation
    • Summary
    • Download from GitHub
  • Themes
  • User Notifications
  • Options
  • Configuration from INI files
  • User Access Control
  • I18N
  • Components and Validation
  • Persistence - JPA
  • Guice & Scopes
  • Event Bus
  • Services
  • Push
  • Create a project Using Eclipse
  • Create a Hierarchy
  • Functional Testing

User Guide

  • Introduction to the User Guide
  • Bootstrap
  • Injector Scope
  • Serialization
  • Forms
  • License

Developer Guide

  • Introduction to the Developer Guide
  • Goals and Objectives
  • Bootstrap
  • Configuration
  • Event Bus
  • Functional Testing
  • Guice Scopes
  • I18N
  • Options and Hierarchies
  • Page Navigation
  • Push
  • Persistence
  • Serialisation
  • Services
  • Testing
  • User Access Control
  • Validation
  • Vertx
  • License

Glossary

  • Glossary
Krail
  • Docs »
  • Page Navigation
  • Edit on GitHub

Page Navigation¶

Clearly we will want to add some new pages, but first we must know what constitutes the definition of a page

Defining a Page¶

A page is represented by a URI, which maps to a specified KrailView class. The name of the page is presented to the user in navigation aware components, so that name must be Locale sensitive. Once the page is defined, it becomes part of the Krail Sitemap, which forms the heart of the navigation system.

There are two ways to add pages to Krail and make use of the navigation features, and you can use either one, or both. These are the “direct” method or “annotation” method. We will use both methods.

Because the page name is locale sensitive, we will first need to provide I18N support.

Introducing I18N¶

You may think that it is premature to be considering I18N at this stage - especially if you are writing an application which will only use one language. However, Krail treats I18N as a first class citizen, and you will find the result of these steps surprisingly useful even in a single Locale application. You could read the full I18N description now, or just follow these steps, as we will come back to I18N later in the Tutorial.

Create an I18N Annotation¶

  • create a package ‘i18n’, under ‘com.example.tutorial’
  • create two Enum classes, one called ‘LabelKey’ and one called ‘DescriptionKey’. Each should implement the I18NKey interface
package com.example.tutorial.i18n;
import uk.q3c.krail.i18n.I18NKey;

public enum LabelKey implements I18NKey {
}
package com.example.tutorial.i18n;
import uk.q3c.krail.i18n.I18NKey;

public enum DescriptionKey implements I18NKey {
}

The names of these classes can be anything, it is the I18NKey interface which is important.

This is all we need for our I18N integration for now, so we can get on with adding pages.

Add a Page - direct method¶

The “direct” method simply means pages are defined directly in a Guice module. We will start by adding some private pages (“private” means they will be available only to authorised users).

  • To keep our pages separate, create a package ‘pages’, under ‘com.example.tutorial’
  • Create a class ‘MyPages’ and extend it from DirectSitemapModule and provide
  • implement the abstract define() method
package com.example.tutorial.pages;

import uk.q3c.krail.core.navigate.sitemap.DirectSitemapModule;

public class MyPages extends DirectSitemapModule{

    @Override
    protected void define() {

    }
}

We will use the define() method to provide our page definitions. We will create three pages, one at the the site root, with two sub-pages, which we want to look something like this in the navigation tree:

> Finance
>> Accounts
>> Payroll
  • enter the following in the define() method
package com.example.tutorial.pages;

import uk.q3c.krail.core.navigate.sitemap.DirectSitemapModule;
import uk.q3c.krail.core.shiro.PageAccessControl;
import com.example.tutorial.i18n.LabelKey;

public class MyPages extends DirectSitemapModule{

    @Override
    protected void define() {
        addEntry("private/finance", FinanceView.class, LabelKey.Finance,
                PageAccessControl.PERMISSION);
        addEntry("private/finance/accounts", AccountsView.class, LabelKey.Accounts,
                PageAccessControl.PERMISSION);
        addEntry("private/finance/payroll", PayrollView.class, LabelKey.Payroll,
                PageAccessControl.PERMISSION);
    }
}

Make sure you get the right LabelKey - there is one in Krail core as well.

You will have compile errors, but let’s look at what these entries mean.

  • The first parameter is the URI segment, and we generally keep to all lowercase. The second and third entries are subpages, so need a qualified path.
  • The second parameter is the class to use as a View - we haven’t created them yet.
  • The third parameter is the page name, is locale-sensitive and therefore an I18NKey
  • The fourth parameter determines what sort of access control is applied to the page. We want controlled access to these pages, so this parameter is set to PERMISSION

We’ll make it easier by extending the Grid3x3ViewBase base class from the Krail core - this just gives us a 3x3 grid to place components in.

Tip

Extending ViewBase or one of its sub-classes is usually the easiest way to create your views, but however you do it, you must implement KrailView. ViewBase can also help with deserialization.

  • create the 3 views we want … AccountsView, FinanceView and PayrollView … just by extending Grid3x3ViewBase and injecting Translate (only FinanceView is shown here):
package com.example.tutorial.pages;

import com.google.inject.Inject;
import uk.q3c.krail.i18n.Translate;
import uk.q3c.krail.core.view.Grid3x3ViewBase;

public class FinanceView extends Grid3x3ViewBase {

    @Inject
    protected FinanceView(Translate translate) {
        super(translate);
    }
}

Defining the I18NKeys¶

By default, if Krail’s I18NProcessor cannot find the value of an I18NKey, it uses the name of the enum instead, with underscores replaced with spaces. This means that as long as you are comfortable with breaking the ‘all-uppercase’ convention for enum constant names, you can get started quickly by not defining any values for the I18NKeys. This is great for prototyping, and even if your application uses a language with accents and diacriticals, the enum name may be good enough for a prototype.

  • Add the required constants to LabelKey
package com.example.tutorial.i18n;

import uk.q3c.krail.i18n.I18NKey;

public enum LabelKey implements I18NKey {
    Accounts, Payroll, Finance
}

Using the Pages¶

Now we have defined the pages in a Guice module, we need to tell the BindingManager to include them:

@Override
   protected void addSitemapModules(List<Module> baseModules) {
       baseModules.add(new SystemAccountManagementPages());
       baseModules.add(new MyPages());
}

View the Pages¶

Run the application again. When the application starts the new pages will not be visible - but that is what we should expect, as we said these pages needed permission to view.

  • Log in (any username, password=’password’), and you will see the pages, under ‘Private’, in the navigation tree and menu.

You may be wondering whether these pages need to be under the ‘Private’ branch. At the moment they do, but only because of the very simple access control rules supplied by DefaultRealm. You can actually define any logical structure, and we will see how to control permissions in the User Access Control section of the Tutorial.

Add a Page - Annotation method¶

The second method of defining a page is to use an annotation on a KrailView implementation. To begin with, we need to tell Krail where to look for annotated views - this reduces the amount of scanning Krail has to do at start up. To do that we:

  • create a new class in the ‘pages’ package, “AnnotatedPagesModule” and extend AnnotationSitemapModule
  • implement the define() method
  • add an entry in the define method, as below:
package com.example.tutorial.pages;

import com.example.tutorial.i18n.LabelKey;
import uk.q3c.krail.core.navigate.sitemap.AnnotationSitemapModule;

public class AnnotatedPagesModule extends AnnotationSitemapModule {

    @Override
    protected void define() {
        addEntry("com.example.tutorial.pages",LabelKey.Accounts);
    }
}

The call to addEntry tells Krail to recursively scan the com.example.tutorial.pages package for classes with a @View annotation. Multiple calls to addEntry can be made. The second parameter should be an I18NKey from the same enum that you are going to use in your @View annotations. The value you supply to the addEntry method is just a sample, it just needs to be from the same class. This is necessary because of the limitations on what Java allows as Annotation parameter types

Now that this is done, any views in the ‘pages’ package, annotated with @View, will be added to the Sitemap.

  • create another view, “PurchasingView” in the pages package, sub-classed from Grid3x3ViewBase:
package com.example.tutorial.pages;

import com.google.inject.Inject;
import uk.q3c.krail.i18n.Translate;
import uk.q3c.krail.core.navigate.sitemap.View;
import uk.q3c.krail.core.shiro.PageAccessControl;
import uk.q3c.krail.core.view.Grid3x3ViewBase;

@View(uri = "private/finance/purchasing",pageAccessControl = PageAccessControl.PERMISSION,labelKeyName = "Purchasing")
public class PurchasingView extends Grid3x3ViewBase {

    @Inject
    protected PurchasingView(Translate translate) {
        super(translate);
    }
}
  • create the ‘Purchasing’ constant for LabelKey ` public enum LabelKey implements I18NKey { Accounts, Payroll, Finance, Purchasing } `
  • tell the BindingManager to include the module we have just created
@Override
   protected void addSitemapModules(List<Module> baseModules) {
       baseModules.add(new SystemAccountManagementPages());
       baseModules.add(new MyPages());
       baseModules.add(new AnnotatedPagesModule());
   }
  • Run the application, log in and you will see that “Purchasing” has been added to the Finance page.

Choosing the Method¶

You can mix Direct and Annotation sitemap entries however you wish, but that can get a bit confusing to manage. Which method you choose is mostly a matter of preference, but there is one feature of the direct method you should be aware of.

Our direct pages module looks currently looks like this: ` addEntry(“private/finance”, FinanceView.class, LabelKey.Finance, PageAccessControl.PERMISSION); addEntry(“private/finance/accounts”, AccountsView.class, LabelKey.Accounts, PageAccessControl.PERMISSION); addEntry(“private/finance/payroll”, PayrollView.class, LabelKey.Payroll, PageAccessControl.PERMISSION); ` There is a lot of repetition in the URIs, so there is an alternative, by setting a rootURI which is applied to all pages:

package com.example.tutorial.pages;

import com.example.tutorial.i18n.LabelKey;
import uk.q3c.krail.core.navigate.sitemap.DirectSitemapModule;
import uk.q3c.krail.core.shiro.PageAccessControl;

public class MyPages extends DirectSitemapModule {

[source,java]
----
public MyPages() {
    rootURI = "private/finance";
}

@Override
protected void define() {
    addEntry("", FinanceView.class, LabelKey.Finance,
            PageAccessControl.PERMISSION);
    addEntry("accounts", AccountsView.class, LabelKey.Accounts,
            PageAccessControl.PERMISSION);
    addEntry("payroll", PayrollView.class, LabelKey.Payroll,
            PageAccessControl.PERMISSION);
}
----

}
  • update MyPages so it is as above
  • run the application and you will see that the pages appear in the same way as before

Moving a Set of Pages¶

We can easily move all the pages of a Direct module by changing the rootUri - they can be moved anywhere in the Sitemap, as a set, as long the Sitemap maintains a logical structure. We will need to keep the finance pages on the “Private” branch for now, because of the Access Control rules, but as an example, let’s suppose we decide that it should have a rootURI of “private/finance-department” instead:

  • modify the Binding Manager as below, to provide a different rootURI as the module is constructed:
@Override
  protected void addSitemapModules(List<Module> baseModules) {
      baseModules.add(new SystemAccountManagementPages());
      baseModules.add(new MyPages().rootURI("private/finance-department"));
      baseModules.add(new AnnotatedPagesModule());
  }
  • modify the annotated view (otherwise the Sitemap will break because there is no longer a “private/finance” page
package com.example.tutorial.pages;

import com.google.inject.Inject;
import uk.q3c.krail.i18n.Translate;
import uk.q3c.krail.core.navigate.sitemap.View;
import uk.q3c.krail.core.shiro.PageAccessControl;
import uk.q3c.krail.core.view.Grid3x3ViewBase;

@View(uri = "private/finance-department/purchasing",pageAccessControl = PageAccessControl.PERMISSION,labelKeyName = "Purchasing")
public class PurchasingView extends Grid3x3ViewBase {

    @Inject
    protected PurchasingView(Translate translate) {
        super(translate);
    }
}
  • Run the application and check that new URI is being used.

Tip

If you do want to set the rootURI directly in the module, you need to do so in the constructor, or it will prevent the fluent method shown above from working.

Tip

This feature of moving blocks of pages is available only with Direct pages. Although it might be possible to do something similar with annotated pages by mapping packages to URIs, there are currently no plans to do so.

Navigation¶

Add some public pages¶

Add a couple of public pages:

  • in the pages package create “MyPublicPages” class, extended from DirectSitemapModule with a couple of pages defined. Note that we are going to put these as ‘roots’ of the tree, as rootUri is set to an empty string.:
package com.example.tutorial.pages;

import com.example.tutorial.i18n.LabelKey;
import uk.q3c.krail.core.navigate.sitemap.DirectSitemapModule;
import uk.q3c.krail.core.shiro.PageAccessControl;

public class MyPublicPages extends DirectSitemapModule {


public MyPublicPages() {
    rootURI = "";
}

@Override
protected void define() {
    addEntry("news", NewsView.class, LabelKey.News, PageAccessControl.PUBLIC);
    addEntry("contact-us", ContactUsView.class, LabelKey.Contact_Us, PageAccessControl.PUBLIC);

}
  • Create the views, extended from`Grid3x3ViewBase`:
package com.example.tutorial.pages;

import com.google.inject.Inject;
import uk.q3c.krail.i18n.Translate;
import uk.q3c.krail.core.view.Grid3x3ViewBase;

public class ContactUsView extends Grid3x3ViewBase {

@Inject
protected ContactUsView(Translate translate) {
    super(translate);
}
package com.example.tutorial.pages;

import com.google.inject.Inject;
import uk.q3c.krail.i18n.Translate;
import uk.q3c.krail.core.view.Grid3x3ViewBase;

public class NewsView extends Grid3x3ViewBase {


@Inject
protected NewsView(Translate translate) {
    super(translate);
}
  • And add the LabelKey constants
public enum LabelKey implements I18NKey {
 Accounts, Payroll, Finance, News, Contact_Us, Purchasing
}
  • Finally, update the BindingManager to include this new set of pages:
@Override
   protected void addSitemapModules(List<Module> baseModules) {
       baseModules.add(new SystemAccountManagementPages());
       baseModules.add(new MyPages().rootURI("private/finance-department"));
       baseModules.add(new AnnotatedPagesModule());
       baseModules.add(new MyPublicPages());
   }

Getting the Navigator¶

We will do just a little bit more with these views to help demonstrate navigation - we’ll just add some buttons to direct us to different URIs. First, though, we need access to Krail’s Navigator. We will inject it into both views, using constructor injection:

package com.example.tutorial.pages;

import com.google.inject.Inject;
import uk.q3c.krail.i18n.Translate;
import uk.q3c.krail.core.navigate.Navigator;
import uk.q3c.krail.core.view.Grid3x3ViewBase;

public class ContactUsView extends Grid3x3ViewBase {

private Navigator navigator;

@Inject
protected ContactUsView(Translate translate, Navigator navigator) {
    super(translate);
    this.navigator = navigator;
}
package com.example.tutorial.pages;

import com.google.inject.Inject;
import uk.q3c.krail.i18n.Translate;
import uk.q3c.krail.core.navigate.Navigator;
import uk.q3c.krail.core.view.Grid3x3ViewBase;

public class NewsView extends Grid3x3ViewBase {

private Navigator navigator;

@Inject
protected NewsView(Translate translate, Navigator navigator) {
    super(translate);
    this.navigator = navigator;
}

Adding some components¶

  • Add buttons and actions in the doBuild method of NewsView:
@Override
 protected void doBuild(ViewChangeBusMessage busMessage) {
    super.doBuild(busMessage);
    Button navigateToContactUsBtn = new Button("Contact Us");
    Button navigateToPrivatePage = new Button("Accounts");
    navigateToContactUsBtn.addClickListener(c -&gt; navigator.navigateTo("contact-us"));
    navigateToPrivatePage.addClickListener(c-&gt;navigator.navigateTo("private/finance-department/accounts"));
    setCentreCell(new VerticalLayout(navigateToContactUsBtn,navigateToPrivatePage));
 }

The first two lines just create the buttons. The second two lines add click listeners, which are set up to use the`Navigator`to direct us to the chosen page. Then the buttons are added to a VerticalLayout which is put in the centre cell of the grid.

  • Run the application, but do not log in.
  • Click on the “News” page
  • Press the “Contact Us” button, and you will be taken to the “Contact Us” page
  • Press the browser back button, and you will be back on the “News” page
  • Press the “Accounts” button - and you a notification will appear to say that the page does not exist. As mentioned earlier, the same notification is given whether you are not authorised or the page does not exist.
  • Log in
  • Press the “Accounts” button again, and as you are now authorised, you will be at the “Accounts” page

Navigating with Parameters¶

A common requirement is to land on a page with parameters - a record id, for example, so the page know which data to load. We are going to add a “Contact Detail” page to simulate this.

  • Just as we’ve done before, add the page to ‘’‘MyPublicPages’‘’, create the view and add the LabelKey constant:
package com.example.tutorial.pages;

import com.example.tutorial.i18n.LabelKey;
import uk.q3c.krail.core.navigate.sitemap.DirectSitemapModule;
import uk.q3c.krail.core.shiro.PageAccessControl;

public class MyPublicPages extends DirectSitemapModule {

public MyPublicPages() {
    rootURI = "";
}

@Override
protected void define() {
    addEntry("news", NewsView.class, LabelKey.News, PageAccessControl.PUBLIC);
    addEntry("contact-us", ContactUsView.class, LabelKey.Contact_Us, PageAccessControl.PUBLIC);
    addEntry("contact-us/contact-detail", ContactDetailView.class, LabelKey.Contact_Detail, PageAccessControl.PUBLIC);
}
package com.example.tutorial.pages;

import com.google.inject.Inject;
import uk.q3c.krail.i18n.Translate;
import uk.q3c.krail.core.view.Grid3x3ViewBase;

public class ContactDetailView extends Grid3x3ViewBase {

@Inject
protected ContactDetailView(Translate translate) {
    super(translate);
}

Receiving parameters¶

To set ContactDetailView up to receive parameters all we need to do is override either the afterBuild method or the loadData method. Using loadData (even if you are not loading data) means you won’t forget to call super.afterBuild() first …​

package com.example.tutorial.pages;

import com.google.inject.Inject;
import com.vaadin.ui.FormLayout;
import com.vaadin.ui.Label;
import uk.q3c.krail.core.view.Grid3x3ViewBase;
import uk.q3c.krail.core.view.component.AfterViewChangeBusMessage;
import uk.q3c.krail.core.view.component.ViewChangeBusMessage;
import uk.q3c.krail.i18n.Translate;

public class ContactDetailView extends Grid3x3ViewBase {
 private Label idLabel;
 private Label nameLabel;

@Inject
protected ContactDetailView(Translate translate) {
    super(translate);
}

@Override
protected void doBuild(ViewChangeBusMessage busMessage) {
    super.doBuild(busMessage);
    idLabel = new Label();
    idLabel.setCaption("id");
    nameLabel = new Label();
    nameLabel.setCaption("name");
    setCentreCell(new FormLayout(idLabel, nameLabel));
}

@Override
protected void loadData(AfterViewChangeBusMessage busMessage) {
    idLabel.setValue(busMessage.getToState()
            .getParameterValue("id"));
    nameLabel.setValue(busMessage.getToState()
            .getParameterValue("name"));
}

The process in loadData() is straightforward. The busMessage is just an event, and it carries a reference to the navigation state we are navigating from, and the state we are navigating to. This is represented by NavigationState, which also contains any parameters that have been passed with the URI.

Sending parameters¶

To send parameters, construct a NavigationState, specifying the parameters to go with it and call Navigator.navigateTo(NavigationState)

  • Update ContactUsView to add a button whose click listener builds the NavigationState, adds parameters, then calls the Navigator.
@Override
protected void doBuild(ViewChangeBusMessage busMessage) {
    super.doBuild(busMessage);
    Button navigateWithParametersBtn = new Button("Navigate with parameters");
    NavigationState navState = new NavigationState().virtualPage("contact-us/contact-detail")
                                                      .parameter("id", "33")
                                                  .parameter("name", "David");
    navigateWithParametersBtn.addClickListener(c->navigator.navigateTo(navState));
    setCentreCell(navigateWithParametersBtn);
}
  • Run the application
  • select “Contact Us”
  • click on “Navigate with Parameters”
  • You will now be at the “Contact Detail” page with the parameter values displayed.

Excluding a page from Navigation¶

If you think about the use of the “Contact Detail” page, it does not actually make sense for it to appear in the navigation components - the only time you would want to access this page is with some parameters to set its contents:

  • Modify the page entry in MyPublicPages, by setting the positionIndex parameter to < 0
   @Override
protected void define() {
          addEntry("news", NewsView.class, LabelKey.News, PageAccessControl.PUBLIC);
          addEntry("contact-us", ContactUsView.class, LabelKey.Contact_Us, PageAccessControl.PUBLIC);
          addEntry("contact-us/contact-detail", ContactDetailView.class, LabelKey.Contact_Detail, PageAccessControl.PUBLIC,-1);
}
  • Run the application, and the page will no longer appear in the navigation components, but is actually still there:
    • Go to the “Contact Us” page
    • Press the “Navigate with Parameters” button
    • The “Contact Detail” page appears as before.

Summary¶

  • You have explored two methods of defining new pages, using Direct and Annotated methods.
  • You have created navigation actions from code
  • You have passed parameters to a page, as you typically might to load data
  • You have excluded a page from navigation, but still make it part of the Sitemap
  • You have “attached” an existing set of pages to a part of the Sitemap different from its default location

Download from GitHub¶

To get to this point straight from GitHub:

git clone https://github.com/davidsowerby/krail-tutorial.git
cd krail-tutorial
git checkout --track origin/krail_0.10.0.0

Revert to commit Pages and Navigation Complete

Next Previous

© Copyright 2018, David Sowerby. Revision cf49e843.

Built with Sphinx using a theme provided by Read the Docs.