Navigation — interacting with a browser's navigation model

Internal paths

Out of the box a Wt application is a single page application: regardless of what the user does, the URL displayed in the location bar does not change. Single page applications are typically implemented in JavaScript and use Ajax to pull data from the server, while multi-page applications typically require full page refreshes.

Single page appliations have a number of draw-backs:

Usability

The usability of your web application may suffer because users lose the ability to use browser history to navigate through the application, and bookmarks to bookmark particular content (or application state). Whether this is an actual usability problem will depend on the type of application: only when a user's intuition is deceived in trying to use browser navigation buttons to navigate through the pages in your application, you should be concerned.

For example, an interactive web application that mimicks a desktop application with a single window whose content is manipulated might be conceptually considered a single page for the user.

On the other hand, applications such as web shops, CMS systems, or online email readers, all have a need for the user to be able to bookmark certain content pages.

Deep linking

There is no possibility for deep linking to content within your application from other websites. This is to some extent related to the previous point. For example a web shop will want its products pages to be linked to from product review websites. But for other applications which have no public information such as email applications, this might not be a motivation for internal paths.

Search engine indexing

Content in your web application cannot be indexed properly by search engines. A related problem, which might be more serious for certain types of applications, is how search engines will perceive your web application. Web robots index the web by retrieving pages and following anchors to other pages. When your application is presented as only a single page, only the content on your initial page is accessible to these search engines.

Wt allows the application to define internal paths and handle changes of internal paths. Because a Wt application is not composed of pages, a URL does not define to particular page, but rather a particular application state. An internal path is a link to a page in a web application.

The widget gallery makes extensive use of internal paths: the top-level menu provides the first level, and the menu to the left a second level.

With the help of HTML5's History API, Wt provides a perfect illusion of a multi-page application, but within a single page!

The recommended method to integrate internal path support in your application is to use internal path links (defined by WLink) in anchors or menu items, for people to navigate to a different 'page' in the application, and respond to internal path change events, to show the new page in response. thus instead of directly listening to a click event, the click results in an internal path change.

The main benefit of this approach is that internal path changes may also originate from a user navigating using the brower's back and forward buttons, may follow such a link in a new window (and thus a new session) or may arrive at the internal path following a deep link from elsewhere, and this approach decouples the origin of the event from the handling of the event.

As a corner stone feature in Wt, internal paths are entirely supported both in Ajax and plain HTML sessions. For browsers without support for the HTML5 History API, Ajax sesion will fall back to named anchors. This effectively makes internal paths the perfect tool for search engine accessibility of your application.

Creating Internal Path Links

For the following widgets you can associate an internal path with the method setLink():

In addition, standard item views such as WTableView will render links for LinkRole data.

In the example below, the push button is configured to navigate to the internal path /navigation/anchor; the next item in the menu bar. The button behaves as an anchor. This is similar to how a WMenuWidget or a WTabWidget works.

Example Next
source
#include <Wt/WLink.h>
#include <Wt/WPushButton.h>

auto button = Wt::cpp14::make_unique<Wt::WPushButton>("Next");
button->setLink(Wt::WLink(Wt::LinkType::InternalPath, "/navigation/anchor"));

In this example, the menu reacts to the internal path event and selects the corresponding menu item. One can also catch the navigation event directly.

Reacting to internal path changes

Whereas in traditional page-based frameworks, a controller or request router is a central component that decides what page content needs to be rendered in response to a particular request, Wt employs a quite different model. Instead, you can react to internal path changes, by listening to the WApplication::internalPathChanged() signal, as you would react to any other event. At any time, including when the application is started, the current internal path is available through WApplication::internalPath().

The following example shows the basic mechanism to react to an internal path event.

Example
Shop Eat

Idle.

source
#include <Wt/WApplication.h>
#include <Wt/WContainerWidget.h>
#include <Wt/WLink.h>
#include <Wt/WAnchor.h>
#include <Wt/WText.h>

namespace {

void handlePathChange(Wt::WText *out)
{
    Wt::WApplication *app = Wt::WApplication::instance();

    if (app->internalPath() == "/navigation/shop")
	out->setText("<p>Currently shopping.</p>");
    else if (app->internalPath() == "/navigation/eat")
	out->setText("<p>Needed some food, eating now!</p>");
    else
	out->setText("<p><i>Idle.</i></p>");
}

}

/*
 * Create two links to internal paths.
 */
auto container = Wt::cpp14::make_unique<Wt::WContainerWidget>();
container->addWidget(
    Wt::cpp14::make_unique<Wt::WAnchor>(
      Wt::WLink(Wt::LinkType::InternalPath, "/navigation/shop"), "Shop"));
container->addWidget(Wt::cpp14::make_unique<Wt::WText>(" "));
container->addWidget(
    Wt::cpp14::make_unique<Wt::WAnchor>(
      Wt::WLink(Wt::LinkType::InternalPath, "/navigation/eat"), "Eat"));

/*
 * Handle the internal path events.
 */
auto out = container->addWidget(Wt::cpp14::make_unique<Wt::WText>());
out->setInline(false);

Wt::WApplication *app = Wt::WApplication::instance();

app->internalPathChanged().connect([=] {
     handlePathChange(out);
});

handlePathChange(out);
        

Notice how the same function is used to react to an actual event but also to the initial state. This will be a common pattern in widgets that support internal path navigation, as they also need to initialize to the initial internal path.