« Back to FrontPage
BreadcrumbPattern

The Breadcrumb Pattern #

The bane of many AJAX sites is the inability to bookmark your current view, and not being able to use the browser's back and forward buttons. Vaadin, by default, is no exception to this affliction.

This Breadcrumb pattern proposes to use the URI fragment (the part that comes after the hash, #, in an URI) in a structured and repeatable way to identify a certain state in an application. With this pattern, your application will be structured in a single, unambiguous tree structure, where each node is reachable from the tree's root.

Using a MVC-like separation pattern together with the Breadcrumb Pattern is very beneficial and therefore highly recommended.

Roles and Responsibilities #

There are only a few roles in this pattern, which should make it easy to understand. There's the Breadcrumbs that is specialized in the question "where are we now, and how did we get here?", and then there's a CrumbTrail that specializes in answering the question "Where can we go from here, and how do we get there?"

I'll describe them here in both a generic form, and then in a Vaadin-specific form:

Abstract #

Breadcrumbs #

A unique-per-user object that knows everything and handles everything regarding to the URI fragment.

Starting from the root of the trail (explicitly known by the Breadcrumbs), the path is iteratively, step by step.

The Breadcrumbs knows nothing about the structural implementation, but only knows the requested trail of paths, and knows how to traverse the trail. The task of actually advancing is left entirely to the CrumbTrails.

Breadcrumbs is also responsible to act as a central navigational tool – there should be methods such that external objects can tell which trail to traverse to. Also backtracking abilities are a must.

The interface of a Breadcrumbs class could look as follows:

public interface Breadcrumbs {
  public void walkTo(String path);
  public void walkBack(int steps);
}

...where Breadcrumbs.walkTo(path) could be used by external objects to move the application forwards one step (several steps shouldn't be allowed, to achieve low cohesion), and Breadcrumbs.walkBack(steps) could be used by external objects to back up in the trail of breadcrumbs an amount of steps.

In addition to the public interface, the Breadcrumbs must be able to listen to and change the URI fragment autonomously. These mechanics are platform specific, and are not covered by this pattern.

CrumbTrail #

A class of objects that have a knowledge of autonavigation though the application.

A CrumbTrail can either lead to another CrumbTrail, or into a 'dead end' (i.e., something other than a CrumbTrail, by definition). The CrumbTrail's responsibility is to know what paths are available, where they lead, and actually shift the application's state there.

It would be natural to have your Application as the trail's root, therefore have your Application implement CrumbTrail. This gives Breadcrumbs a trivial and consistent way of reaching the root of the trail, by accessing its getApplication(), and casting the returned Application into a CrumbTrail. This, however, is by no means a requirement.

The simple interface of the CrumbTrail shouldn't be much more complex than:

public interface CrumbTrail {
  public CrumbTrail walkTo(String path);
}

...where CrumbTrail.walkTo(path) is called solely by the Breadcrumbs object to change the state of the application, if required.

Specific for Vaadin #

Breadcrumbs #

The natural way to implement the Breadcrumbs' role is to create a class that extends the UriFragmentUtility Component. The implementation should implement FragmentChangedListener, and it should listen to itself. This leads to that this object can now autonomously notice any changes in the URI fragment without any additional effort. The URI Fragment can be also be changed with UriFragmentUtility.setFragment(String newFragment).

Whenever a FragmentChangeEvent is fired, this component should, starting from the root, iteratively call the CrumbTrail's walkTo() method, once for each component in the fragment. Each walkTo() invocation results in either a null or a new CrumbTrail, in which case, the next fragment-component is passed to this one. This continues until either the URI fragment is fully traversed, or walkTo() doesn't return a CrumbTrail.

The Breadcrumbs component should be added to the application's main window and be kept there throughout the application's lifetime.

CrumbTrail #

CrumbTrail should be an interface that has at least one method: walkTo(String path), returning another CrumbTrail.

This interface would be implemented by various Vaadin Components, used throughout your application.

The naïve implementation of a CrumbTrail is to rebuild the whole view from scratch each time the trail is walked, by first calling removeAllComponents() in the active ComponentContainer. While this works well with smaller and lighter applications, this can (note: this hasn't been benchmarked yet) lead to very poor performance in applications with deep URI trails.

A better implementation would be for each CrumbTrail class to be stateful, and able to intelligently switch between components, if at all requried. This eliminates much of the overhead, and enables you to effectively do a NOOP if the same URI is requested twice in a row.

A naïve implementation of the interface could look something like:

// ...
public CrumbTrail walkTo(String path) {
  layout.removeAllComponents();

  if ("preferences".equals(path)) {
    PreferencesWindow component = new PreferencesWindow(); // implements CrumbTrail
    addComponent(component);
    return component;
  } else if ("newMessage".equals(path)) {
    getApplication().getMainWindow().showNotification("You can't write new messages");
  }

  return null;
}
// ...

Additional Considerations #

Your components most probably want at some point access the Breadcrumbs object, to control your application. This poses a bit of a problem, because your Breadcrumbs object most probably resides in your Application class. Most of the time, you can reach it by using AbstractComponent.getApplication(), but not always. For example, you can't use it reliably from a Window's CloseListener, because a Window isn't attached anymore to the Application when the event fires, thus returning null. It would be very convenient if the Breadcrumbs component could be made into a static field in the main Application, but this won't work in a multi-user environment with Vaadin.

This leads to two design requirements for your application: Your application must implement TransactionListener, and you need to store the application on each transaction start into a ThreadLocal object, which would be retrieved by a public static method.

You can now, using this static method, reliably retrieve your Breadcrumbs object, and control it throughout your application.

public class MyApp extends Application implements TransactionListener {

  private static final ThreadLocal<MyApp> application = new ThreadLocal<MyApp>();
  private final Breadcrumbs breadcrumbs = new Breadcrumbs();

  public void init() {
    // mainWindow initialization omitted
    getContext().addTransactionListener(this);
    getMainWindow().addComponent(breadcrumbs);
  }

  public void transactionStart(Application application, Object transactionData) {
    if (application == this)
      MyApp.application.set(this);
  }

  public static MyApp getCurrent() { return application.get(); }
  public Breadcrumbs getBreadcrumbs() { return breadcrumbs; }

  public void transactionEnd(Application application, Object transactionData) {}
}

Example by Animation #

Browsed URL: [http://example.com/Application#foo/bar]

  1. Breadcrumbs splits the string "foo/bar" into its path components: [ foo, bar ].
  2. Breadcrumbs retrieves the trail's root, names it trail
  3. Breadcrumbs tells trail to go to "foo".
    1. trail, being a CrumbTrail, recognizes the path "foo", adds a component into view and returns a new CrumbTrail object
  4. Breadcrumbs receives the new CrumbTrail, names it trail
  5. Breadcrumbs tells trail to go to "bar".
    1. trail, being a CrumbTrail, recognizes the path "bar", adds a component into view and returns null.
  6. Breadcrumbs receives the null, stops executing.

Proof of Concept Implementation #

Attached is a Eclipse project with a reference implementation of the pattern. The code is released under the Apache 2.0 License. Or you can play around with the implementation at http://henrik.virtuallypreinstalled.com/BreadCrumbPattern

1 Attachment
7217 Views
Average (0 Votes)
Comments