Extending the Viewer

The Wicket viewer allows you to customize the GUI in several (progressively more sophisticated) ways.

In the customisation chapter described tweaking the UI using custom CSS and custom JavaScript.

In this chapter we have a number of more heavy-weight approaches:

Custom Bootstrap theme

The Apache Isis Wicket viewer uses Bootstrap styles and components, courtesy of the Wicket Bootstrap integration.

By default the viewer uses the default bootstrap theme. It is possible to configure the Wicket viewer to allow the user to select other themes provided by bootswatch.com, and if required one of these can be set as the default.

However, you may instead want to write your own custom theme, for example to fit your company’s look-n-feel/interface guidelines. This is done by implementing Wicket Bootstrap's de.agilecoders.wicket.core.settings.ITheme class. This defines:

  • the name of the theme

  • the resources it needs (the CSS and optional JS and/or fonts), and

  • optional urls to load them from a Content Delivery Network (CDN).

To make use of the custom ITheme the application should register it by subclassing IsisWicketApplication (also register this in web.xml) and add the following snippet:

public void init() {
    ...
    IBootstrapSettings settings = new BootstrapSettings();
    ThemeProvider themeProvider = new SingleThemeProvider(new MyTheme());
    settings.setThemeProvider(themeProvider);
    Bootstrap.install(getClass(), settings);
}

Replacing page elements

Replacing elements of the page is the most powerful general-purpose way to customize the look-n-feel of the viewer. Examples include the Gmap3, Fullcalendar, Excel Download and pdf.js components.

The pages generated by Apache Isis' Wicket viewer are built up of numerous elements, from fine-grained widgets for property/parameter fields, to much larger components that take responsibility for rendering an entire entity, or a collection of entities. Under the covers these are all implementations of the the Apache Wicket Component API. The larger components delegate to the smaller, of course.

How the viewer selects components

Components are created using Apache Isis' ComponentFactory interface, which are registered in turn through the ComponentFactoryRegistrar interface. Every component is categorizes by type (the ComponentType enum), and Apache Isis uses this to determine which ComponentFactory to use. For example, the ComponentType.BOOKMARKED_PAGES is used to locate the ComponentFactory that will build the bookmarked pages panel.

Each factory is also handed a model (an implementation of org.apache.wicket.IModel) appropriate to its ComponentType; this holds the data to be rendered. For example, ComponentType.BOOKMARKED_PAGES is given a BookmarkedPagesModel, while ComponentType.SCALAR_NAME_AND_VALUE factories are provided a model of type of type ScalarModel .

In some cases there are several factories for a given ComponentType; this is most notably the case for ComponentType.SCALAR_NAME_AND_VALUE. After doing a first pass selection of candidate factories by ComponentType, each factory is then asked if it appliesTo(Model). This is an opportunity for the factory to check the model itself to see if the data within it is of the appropriate type.

Thus, the BooleanPanelFactory checks that the ScalarModel holds a boolean, while the JodaLocalDatePanelFactory checks to see if it holds org.joda.time.LocalDate.

There will typically be only one ComponentFactory capable of rendering a particular ComponentType/ScalarModel combination; at any rate, the framework stops as soon as one is found.

There is one refinement to the above algorithm where multiple component factories might be used to render an object; this is discussed in Additional Views of Collections, below.

How to replace a component

This design (the chain of responsibility design pattern) makes it quite straightforward to change the rendering of any element of the page. For example, you might switch out Apache Isis' sliding bookmark panel and replace it with one that presents the bookmarks in some different fashion.

First, you need to write a ComponentFactory and corresponding Component. The recommended approach is to start with the source of the Component you want to switch out.

The ComponentFactory should be annotated as a Spring service, typically using Spring’s @Service annotation.

For example:

import org.springframework.stereotype.Service;

@Service
public class MyBookmarkedPagesPanelFactory extends ComponentFactoryAbstract {
    public MyBookmarkedPagesPanelFactory() {
        super(ComponentType.BOOKMARKED_PAGES);
    }
    @Override
    public ApplicationAdvice appliesTo(final IModel<?> model) {
        return appliesIf(model instanceof BookmarkedPagesModel);
    }
    @Override
    public Component createComponent(final String id, final IModel<?> model) {
        final BookmarkedPagesModel bookmarkedPagesModel = (BookmarkedPagesModel) model;
        return new MyBookmarkedPagesPanel(id, bookmarkedPagesModel);
    }
}

and

public class MyBookmarkedPagesPanel
    extends PanelAbstract<BookmarkedPagesModel> {
   ...
}

Here PanelAbstract ultimately inherits from org.apache.wicket.Component. Your new Component uses the information in the provided model (eg BookmarkedPagesModel) to know what to render.

Your new component will be used instead of the default implementation.

Additional Views of Collections

As explained above, in most cases Apache Isis' Wicket viewer will search for the first ComponentFactory that can render an element, and use it. In the case of (either standalone or parented) collections, though, the viewer will show all available views.

For example, out-of-the-box Apache Isis provides a table view, a summary view (totals/sums/averages of any data), and a collapsed view. These are selected by clicking on the toolbar by each collection.

Additional views though could render the objects in the collection as a variety of ways; as illustrated by the Gmap3, Fullcalendar and Excel Download extensions.

Wicket itself has lots of components available at its wicketstuff.org companion website; you might find some of these useful for your own customizations.

Custom object view (eg dashboard)

One further use case in particular is worth highlighting; the rendering of an entire entity. Normally for entities this is done using Bs3GridPanelFactory, this being the first ComponentFactory for the ComponentType.ENTITY that is registered in Apache Isis default ComponentFactoryRegistrarDefault.

You could, though, register your own ComponentFactory for entities that is targeted at a particular class of entity - some sort of object representing a dashboard, for example. It can use the EntityModel provided to it to determine the class of the entity, checking if it is of the appropriate type.

The demo app (jdo or jpa) includes an example of this technique (Featured > Where in the World).

Custom pages

In the vast majority of cases customization should be sufficient by replacing elements of a page. However, it is also possible to define an entirely new page for a given page type.

The Wicket viewer defines these page types (see the org.apache.isis.viewer.wicket.model.models.PageType enum):

Table 1. PageType enum
Page type Renders

SIGN_IN

The initial sign-in (aka login) page

SIGN_UP

The sign-up page (if user registration is enabled).

SIGN_UP_VERIFY

The sign-up verification page (if user registration is enabled; as accessed by link from verification email)

PASSWORD_RESET

The password reset page (if enabled).

HOME

The home page, displaying either the welcome message or dashboard

HOME_AFTER_PAGETIMEOUT

Variation on home page after a timeout.

ABOUT

The about page, accessible from link top-right

ENTITY

Renders a single entity or view model

STANDALONE_COLLECTION

Page rendered after invoking an action that returns a collection of entites

VALUE

After invoking an action that returns a value type (though not URLs or Blob/Clobs, as these are handled appropriately automatically).

VOID_RETURN

After invoking an action that is void

ACTION_PROMPT

(No longer used).

The PageClassList interface declares which class (subclass of org.apache.wicket.Page is used to render for each of these types. For example, Apache Isis' WicketSignInPage renders the signin page.

To specify a different page class, create a new implementation of PageClassList and annotate with an earlier precedence than the default. If you are just tweaking the defaults, then its easiest to override PageClassListDefault:

@Service
@Order(OrderPrecedence.EARLY)
public class MyPageClassList extends PageClassListDefault {
    protected Class<? extends Page> getSignInPageClass() {
        return MySignInPage.class;
    }
}