Skip to content

06. Application

github-actions[bot] edited this page Jan 15, 2026 · 41 revisions

Application

Every Nalu application needs one application interface. The application interface defines the basic behavior of Nalu.

Application interface

To implement Nalu in your application, first create an application interface:

@Application(loader = MyLoader.class,
             startRoute = "/application/search",
             context = MyApplicationContext.class)
interface MyApplication
    extends IsApplication {
}

The application interface must extend IsApplication and needs the @Application annotation.

The attributes of the Application annotation are:

  • startRoute: The start route is the initial route that is called in case the application is called without a hash. If the application is called with a hash, the startRoute-attribute has no effect.

  • loader: the application loader will be executed at application start and before loading the modules. (see: Application Loader). This attribute is optional.

  • postLoader: the application post loader will be executed at application start and after all modules have been added and all module loaders have been executed. (see: Application Loader). This attribute is optional.

  • context: the application context is a singleton that will be injected in every filter, controller and handler of the application (see: Context).

  • usingHistory: if true Nalu supports history, so the application can reload. Otherwise, no hash will be created and the url stays untouched. This might be useful, in case the application should never reload or will be used embedded inside another web site. (default: true)

  • usingHash: if true Nalu uses a hash token, otherwise a hash-less token (Clean URLs). (default: true)

  • usingUnderscoreForParametersInUrl: if true Nalu uses a '_' before a parameter inside the url (default: false)

  • stayOnSide: if true Nalu will - in case of an empty hash - add a new history entry using the hash of the start route

  • alertPresenter: Tells Nalu to use a custom alert - this parameter is optional

  • confirmPresenter: Tells Nalu to use a custom confirm - this parameter is optional

  • illegalRouteTarget: Tells Nalu to use this route instead showing an error message - this parameter is optional

  • usingBaseHref: if true Nalu detects and sets the base href for the application. This is especially useful if hash-less routing (Clean URLs) is activated and the resources (images, documents etc.) are linked relative (default: false)

  • usingTrailingSlash: if true the hash will end with an '/'. if false there will be no trailing slash at the end of the hash (default: false)


Clean URLs (Hash-less Routing) (since v4.0.0)

Clean URLs (also called hash-less routing) allow your application to use readable, bookmarkable URLs without the hash symbol (#). This approach is recommended for modern web applications, especially when you want better SEO, shareable links, or integration with server-side rendering.

Comparison:

Approach Example URL Pros Cons
Hash-based http://localhost:8080/index.html#/app/route No server config needed Not SEO-friendly
Clean URL http://localhost:8080/app/route SEO, bookmarks, readable Server roundtrip, needs server config,

How to enable Clean URLs in Nalu

  • Set usingHash = false in your @Application annotation.
  • (Optional) Set usingBaseHref = true if you use relative resources.
  • Choose the CleanUrlWithParameterHandlingCoreWebPlugin-class to enable and parameter handling.

Adding the Plugin

By default Nalu uses the DefaultCorePlugin for hash-handling. To enable the clean URL feature, you need to config the Application annotation as mentioned above and change the Core-Plugin to CleanUrlWithParameterHandlingCoreWebPlugin by calling: NaluCorePluginFactory.INSTANCE.registerPlugin(new CleanUrlWithParameterHandlingCoreWebPlugin());. A great place to change the default-behavior is inside the onModuleLoad-method of your application class just before calling the run-method.

Server-side configuration for Clean URLs

Because Clean URLs change the browser's address bar, your server must handle requests to these routes and redirect them to your application's entry point (usually index.html). Otherwise, reloading or deep-linking will result in a 404 error.

Example: Spring Boot Filter for Clean URLs

The following filter redirects unknown application routes to index.html, passing the route as a parameter:

@Component
@Order(1)
public class RedirectFilter extends GenericFilterBean {
  // List your shell names here
  private final String[] CLIENT_URI_STARTS = new String[] {"/login", "/app"};

  @Override
  public void doFilter(
      ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain)
      throws IOException, jakarta.servlet.ServletException {
    HttpServletRequest request = (HttpServletRequest) servletRequest;
    HttpServletResponse response = (HttpServletResponse) servletResponse;

    String requestUri = request.getRequestURI();
    String queryString = request.getQueryString();
    String contextPath = request.getContextPath();

    String uriAfterContext = requestUri.substring(contextPath.length());
    String matchedPrefix = getMatchingClientUriStart(uriAfterContext);

    if (matchedPrefix != null) {
      StringBuilder redirectUrl = new StringBuilder();
      redirectUrl.append(contextPath).append("/?uri=");
      redirectUrl.append(URLEncoder.encode(uriAfterContext, StandardCharsets.UTF_8.name()));

      if (queryString != null && !queryString.isEmpty()) {
        redirectUrl.append("&").append(queryString);
      }
      response.sendRedirect(redirectUrl.toString());
      return;
    }
    filterChain.doFilter(servletRequest, servletResponse);
  }

  /**
   * Checks if the URI (after context path) starts with any of the patterns in CLIENT_URI_STARTS.
   *
   * @param uriAfterContext path to check for client uri starts
   * @return the matched pattern (e.g. "/app") or null.
   */
  private String getMatchingClientUriStart(String uriAfterContext) {
    for (String clientUriStart : this.CLIENT_URI_STARTS) { {
      if (uriAfterContext.startsWith(clientUriStart)) {
        return clientUriStart;
      }
    }
    return null;
  }
}

How it works:

  • The filter checks if the request matches an application route (by shell name).
  • If so, it redirects to index.html?uri=[route].
  • Nalu then uses the uri parameter as a start route.

Troubleshooting:

  • If you get 404 errors on reload or deep links, your server is not redirecting unknown routes to your SPA entry point.
  • Make sure all application routes are handled by your filter or server config.
  • For other server frameworks (Node.js, nginx, Apache), configure a catch-all route to serve index.html.

Start Route

The start route is the route, that will be used in case the application is called without a hash or URI.

Minimal requirements for a start route are:

  • needs a shell
  • needs a part to identify a controller

Important note: a start route can not have a parameter!

Using only a shell as a start route will produce an error.

Custom alert and confirm

Nalu offers the possibility to add a custom alert and confirm dialog. In case it is set, Nalu will use the custom dialog instead of the default dialog from the plugin.

A custom alert needs to implement the IsCustomAlertPresenter and a custom confirm dialog needs to implement the IsCustomConfirmPresenter.

Filter Annotation

Nalu allows you to use filters to stop routings in order to interrupt a route before it is handled. In case a route is interrupted, you can redirect to another route.

To create a filter, add the @Filter-annotation to the application interface.

Nalu supports the use of more than one filter.

Creating Filters

To create a filter, you need to:

  1. implement the IsFilter-interface
  2. override the filter-method: this method enables you to stop a routing. If filterEvent method returns false, the route is stopped, otherwise, it is forwarded to the router.
  3. override the redirect-method: in case the routing is stopped, the route returns the new route to go to.
  4. override the parameters-method: the parameters of the route. In case there are no parameters, return String[]{}.

Nalu will inject the context into a filter.

Example of a filter:

import java.util.Objects;

public class MyFilter
    extends AbstractFilter<MyContext> {

  @Override
  public boolean filter(String route,
                        String... params) {
    // checking f.e. a route
    if ("/myShell/myRoute01".equals(route)) {
      // interrupt routing
      return false;
    }
    // checking for example a parameter
    if (params.length > 0) {
      if ("Bart".equals(params[0])) {
        // interrupt routing
        return false;
      }
    }
    // routing ok!
   return true;
  }

  @Override
  public String redirectTo() {
    return "/redirectShell/redirectRoute";
  }

  @Override
  public String[] parameters() {
    return new String[] {};
  }

}

Adding Filters

Once you have created a filter, you need to tell Nalu that there is at least one filter and that Nalu has to use it. This can be done thanks to the @Filters-annotation, which can be used on your application interface.

@Filters(filterClasses = { MyFilter01.class, MyFilter02.class })
interface MyApplication
    extends IsApplication {
}

The @Filters annotation will only be handled if the interface is annotated with @Application!

The annotation @Filters has the following attributes:

  • filterClasses: set one or several filters to use. An instance will be created for each class specified.

In case you have more than one filter, filters will be executed in an unconditional order!

Logger Annotation (since version v2.1.0)

Nalu has a logger feature to log information to the browser console or to the server (service needs to be provided).

To activate the log feature, you need to annotate your application class with @Logger:

@Logger(clientLogger = DefaultElemental2ClientLogger.class,
        logger = MyLogger.class)
interface MyApplication
    extends IsApplication {
}

The @Logger annotation will only be handled if the interface is also annotated with @Application!

@logger-annotation has the following attributes:

  • clientLogger: defines the class of the client side logger to use depending on the selected plugin
  • logger: defines the class of the logger which can be used to send log messages to the server

Every plugin provides a default client logger. In case you do not want to create an own logger, use:

  • gwt-plugin-elemental2: DefaultElemental2Logger.class
  • gwt-plugin-elemento: DefaultElementoLogger.class
  • gwt-plugin-gwt: DefaultGWTLogger.class

The logger needs to implement the IsLogger-interface and looks like this:

public class MyLogger
    extends AbstractLogger<MyContext> {
  
  @Override
  public void log(List<String> messages,
                  boolean sdmOnly) {
    LoggingServiceFactory.INSTANCE.log(messages)
                                  .onSuccess(response -> {
                                  })
                                  .onFailed(failed -> {
                                  })
                                  .send();
  }
  
}

This example uses Domino-rest to send the messages to server.

A log message can be triggered by firing a LogEvent.

Version and Build Time

Nalu supports setting a version String using an annotation and storing the build time. To get this feature, the context needs to extend AbstractModuleContext and inside the application interface you have to add the Version-annotation.

@Application(loader = MyLoader.class,
             startRoute = "/application/search",
             context = MyApplicationContext.class)
@Version("1.0.1")             
interface MyApplication
    extends IsApplication {
}

The example above will set the version of the application to '1.0.1'. To access the application version, use: context.getApplicationVersion(). To access the build time call context.getApplicationBuildTime().

Important:
To access the build time, your context need to extend the AbstractModuleContext-class.

It is possible to override the value from the version annotation from the command line. Nalu will look for a property called "nalu.application.version". If the property exists, Nalu will use this value. This will only work, in case the Version annotation is used.

The following set up inside the client pom (in a multi module project) will provide the maven project version to Nalu:

      <plugin>
        <groupId>org.codehaus.mojo</groupId>
        <artifactId>properties-maven-plugin</artifactId>
        <version>1.0.0</version>
        <executions>
          <execution>
            <goals>
              <goal>set-system-properties</goal>
            </goals>
            <configuration>
              <properties>
                <property>
                  <name>nalu.application.version</name>
                  <value>${project.version}</value>
                </property>
              </properties>
            </configuration>
          </execution>
        </executions>
      </plugin>

Not sure, if this is the best approach, but it works. In case you find a more elegant implementation, let me know.

Debug Annotation (removed in version v2.1.0)

Nalu integrates a log feature that lets you trace the routes handled, controllers used, fired events, etc. The debug messages will be displayed using the browser's console.

This feature is only available during development!

To activate the log feature, you need to annotate your application class with @Debug:

@Debug()
interface MyApplication
    extends IsApplication {
}

Clone this wiki locally