Search code examples
meteoriron-router

Meteor + Iron Router to create breadcrumbs


Ok, so I found this post: Meteor breadcrumb

But lets say I have the following:

<template name="somePage">
    <h1>Page Title</h1>
    {{> breadcrumb}}
</template>

<template name="breadcrumb">
    <ul class="breadcrumb">
        <li>
            <a href="{{pathFor 'homeTemplate'}}">Home</a>
        </li>
        {{#each path}}
        <li>
            <a href="needthepath">{{this}}</a>
        </li>
    </ul>
</template>

Helper:

Template.breadcrumb.helpers({
    path: function() {
        return Router.current().path.split( "/" );
    }
});

Ok so the linked question at the top got me the basics. I'm trying to understand how to do a few more things here that should be obvious. I want the first

  • to be for the home page, and the result returned from the path: function() includes an empty "", "page", "page", etc. in the beginning of it.

    I'd like to be able to incorporate the proper paths. To be clear, I'd love to pull this off:

    <template name="breadcrumb">
        <ul class="breadcrumb">
            <li>
                <a href="{{pathFor 'homeTemplate'}}">Home</a>
            </li>
            ~ pseudo logic
            {{#each path that isn't current page}}
            <li>
                <a href="{{dynamicPath}}">{{this}}</a>
            </li>
            {{/each}}
            <li>
                {{ currentPage }}
            </li>
        </ul>
    </template>
    

    Has anyone done this or found a reference that I haven't stumbled across yet?


  • Solution

  • I'll give you my own recipe for breadcrumbs using iron:router.

    It works by supplying additional options to your routes in order to establish a hierarchy between them, with parent-children relations. Then we define a helper on the Router to give us a list of parent routes (up to home) for the current route. When you have this list of route names you can iterate over them to create your breadcrumbs.

    First, we need to define our breadcrumbs template which is actually very similar to your pseudo-code. I'm using bootstrap and font-awesome, as well as some newly introduced iron:router@1.0.0-pre features.

    <template name="breadcrumbs">
        <ol class="breadcrumb">
            <li>
                {{#linkTo route="home"}}
                    <i class="fa fa-lg fa-fw fa-home"></i>
                {{/linkTo}}
            </li>
            {{#each intermediateRoutes}}
                <li>
                    {{#linkTo route=name}}
                        <strong>{{label}}</strong>
                    {{/linkTo}}
                </li>
            {{/each}}
            <li class="active">
                <strong>{{currentRouteLabel}}</strong>
            </li>
        </ol>
    </template>
    

    The {{#linkTo}} block helper is new in iron:router@1.0.0-pre, it simply outputs an anchor tag with an href attribute which value is {{pathFor "route"}}.

    Let's define the helpers from our breadcrumbs template:

    Template.breadcrumbs.helpers({
        intermediateRoutes: function() {
            if (!Router.current()) {
                return;
            }
            // get rid of both the first item, which is always assumed to be "home",
            // and the last item which we won't display as a link
            var routes = Router.parentRoutes().slice(1, -1);
            return _.map(routes, function(route) {
                // extract name and label properties from the route
                return {
                    name: route.getName(),
                    label: route.options.label
                };
            });
        },
        currentRouteLabel: function() {
            // return the label property from the current route options
            return Router.current() && Router.current().route.options.label;
        }
    });
    

    Notice that we rely on the existence of a special option named 'label' which represents what we're going to put in our anchors, we could also have used the name for testing purpose.

    The parentRoutes method is something we need to extend the Router with:

    _.extend(Router, {
      parentRoutes: function() {
        if (!this.current()) {
          return;
        }
        var routes = [];
        for (var route = this.current().route; !_.isUndefined(route); route = this.routes[route.options.parent]) {
          routes.push(route);
        }
        return routes.reverse();
      }
    });
    

    Again, this function assumes that every route (except "home") has a parent property which contains the name of its parent route, we then iterate to traverse the route hierarchy (think of a tree, like a file system structure) from the current route up to the root route, collecting each intermediate route in an array, along with the current route.

    Finally, don't forget to declare your routes with our two additional properties that our code relies on, along with a name which is now mandatory as routes are indexed by name in the Router.routes property:

    Router.route("/", {
      name: "home"
    });
    Router.route("/nested1", {
      name: "nested1",
      parent: "home"
    });
    Router.route("/nested1/nested2", {
      name: "nested2",
      parent: "nested1"
    });
    // etc...
    

    This example is pretty basic and certainly doesn't cover every use case, but should give you a solid start in terms of design logic toward implementing your own breadcrumbs.