Meteor + Iron Router to create breadcrumbs - meteor

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>
Home
</li>
{{#each path}}
<li>
{{this}}
</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>
Home
</li>
~ pseudo logic
{{#each path that isn't current page}}
<li>
{{this}}
</li>
{{/each}}
<li>
{{ currentPage }}
</li>
</ul>
</template>
Has anyone done this or found a reference that I haven't stumbled across yet?

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.

Inspired by #saimeunt I created a meteor breadcrumb plugin which can be found here: https://atmospherejs.com/monbro/iron-router-breadcrumb. You also specify a parent route and a title for the route itself.

I used saimeunt answer but had to make small changes to the template and the template helpers because I have parameters in some of my route paths. Here are my changes.
Template changes: add data=getParameter to #linkTo for intermediate routes
<template name="breadcrumbs">
<ol class="breadcrumb">
<li>
{{#linkTo route="dashboard"}}
<i class="fa fa-lg fa-fw fa-home"></i>
{{/linkTo}}
</li>
{{#each intermediateRoutes}}
<li>
{{#linkTo route=name data=getParameters}}
<strong>{{label}}</strong>
{{/linkTo}}
</li>
{{/each}}
<li class='active'>
<strong>{{currentRouteLabel}}</strong>
</li>
</ol>
</template>
Template helper changes: add helper function getParameters to get parameters from current route.
Template.breadcrumbs.helpers({
intermediateRoutes: function () {
if (!Router.current()) {
return;
}
var parentRoutes = Router.parentRoutes();
var routes = parentRoutes.slice(1, -1);
var intermediateRoutes = _.map(routes, function (route) {
return {
name: route.getName(),
label: route.options.label
};
});
return intermediateRoutes;
},
currentRouteLabel: function () {
var currentRouteLabel = Router.current() && Router.current().route.options.label;
return currentRouteLabel;
},
getParameters: function(){
var currentRoute = Router.current();
var parameters = currentRoute.params;
return parameters;
}
});

Related

How to fetch query parameters in a template?

I am using Meteor 1.2.1 + iron-router with autopublish turned on
I would like to construct an anchor href based on collection values returned by a helper and query params
Is it possible to do this in the template tag, e.g. query_param1 should be read from the URL?
<template name="dataEntry">
{{#each data}}
<li>
<a href="/listing?name={{name}}&color=<query_param1>">
Data name
</a>
</li>
{{/each}}
</template>
Above, {{name}} is returned by the collection and query parameters are appended to that to create a full hyperlink for the href.
You can use #Stephen's suggestion like this.
In your template html,
<template name="dataEntry">
{{#each data}}
<li>
<a href="/listing?{{queryParams}}">
Data name
</a>
</li>
{{/each}}
</template>
In your template JS,
Template.dataEntry.helpers({
"queryParams": function () {
var name = "";
//get name from collection here like...
//name = Meteor.user().profile.firstName;
var color = Router.current().params.color;
return "name=" + name + "&color=" + color;
}
});
Or you can use two separate helpers
In your template html,
<template name="dataEntry">
{{#each data}}
<li>
<a href="/listing?name={{name}}&color={{color}}">
Data name
</a>
</li>
{{/each}}
</template>
In your template JS,
Template.dataEntry.helpers({
"name": function () {
var name = "";
//get name from collection here like...
//name = Meteor.user().profile.firstName;
return name;
},
"color": function () {
return Router.current().params.color;
}
});
You can access an IronRouter param through a helper, e.g.:
Router.current().params.query_param1

Accessing data from outside events callback

<template name="SideNav">
<ul class='block-items white'>
{{#each blocks}}
<li class='block-item'>
<i class="fa fa-fw fa-folder"></i>
<i class="fa fa-fw fa-folder-open"></i>
<a href='#' class='block-item-link'>
{{name}}
...
{{/each blocks}}
</template>
Given this, I can access each block-item's id when it's clicked by doing
Template.SideNav.events({
"click .block-item": function (e, tem) {
//var blockItemId = this._id;
}
});
How can I borrow the same feature from other places, like onRendered()? Take a look at the following example:
Template.SideNav.onRendered(function() {
this.$('.block-items').sortable({
update: function (e, ui) {
_.each($('.block-item'), function (blockItem) {
// How do I get blockId?
})
console.log("block item rearranged");
}
});
update is a callback function that's invoked when there was a change in the order of block items in the ul list. I need a way to iterate through all the block items and get their corresponding Mongo id's. How can I do it?
Related Documents:
The context of "this" in Meteor template event handlers (using Handlebars for templating)
Update: the "Meteor way"
If you are looking for how Blaze gets this data context for events and helpers, it turns out there is a magical Blaze.getData() function which takes a Blaze view or a DOM object and returns its data context. As far as I could tell by looking at the code, it seems to be the tool Blaze uses for providing data contexts to helpers and events.
So in your case, you could use:
Template.SideNav.onRendered(function() {
this.$('.block-items').sortable({
update: function (e, ui) {
_.each($('.block-item'), function (blockItem) {
var context = Blaze.getData(blockItem.get(0));
var blockId = context._id;
})
console.log("block item rearranged");
}
});
Original answer
An easy way to get the id of a document when working with DOM manipulation (other than blaze events) is to explicitly set it as an attribute in your template, such as:
<template name="SideNav">
<ul class='block-items white'>
{{#each blocks}}
<li class='block-item' id='{{_id}}'>
<i class="fa fa-fw fa-folder"></i>
<i class="fa fa-fw fa-folder-open"></i>
<a href='#' class='block-item-link'>
{{name}}
...
{{/each blocks}}
</template>
This way, you can just fetch the id using jquery's attr method:
Template.SideNav.onRendered(function() {
this.$('.block-items').sortable({
update: function (e, ui) {
_.each($('.block-item'), function (blockItem) {
var blockId = blockItem.attr('id');
})
console.log("block item rearranged");
}
});

How to create simple userProfile route in Meteor with Iron Router

I am trying to route to the current user's profile page using Iron Router, but the 'pathFor' Spacebars helper is not passing the href attribute for the a tag.
This is my template:
<div class="collapse navbar-collapse" id="navigation">
<ul class="nav navbar-nav">
{{#if currentUser}}
<li>Submit Request</li>
<li>View Profile</li>
{{/if}}
</ul>
<ul class="nav navbar-nav navbar-right">
{{> loginButtons}}
</ul>
</div>
The href for the Submit Request button is passing in just fine, but for some reason the View Profile href is not getting passed in.
My router.js is as follows:
Router.route('/users/:_id', {
name: 'userProfile',
data: function() {
return Meteor.users.findOne(this.params._id);
}
});
In my app, I am routing to single "request" page (I created a collection called requests) by doing
Router.route('/requests/:_id, {
name: 'requestPage',
data: function() {
return Requests.findOne(this.params._id);
}
});
This routes correctly to a single request from the Requests collection, so I am confused why the userProfile route doesn't do the same.
Also, I tried to force this functionality by creating a click event on the template:
Template.header.events({
'click .userProfile': function(e) {
e.preventDefault();
var user = Meteor.user();
Router.go('userProfile', {_id: user._id});
}
});
..and this passed the correct href in to the template.
Any help on this would be greatly appreciated!
The userProfile route needs an id parameter. When you call pathFor in a template with only the route name it will attempt to extract the parameters from the current context. Because the context isn't a user (it's a request?) you will end up with an invalid path.
You can set the context inline:
<li>View Profile</li>
Alternatively, you can set the context outside of the call using with:
{{#with currentUser}}
<li>View Profile</li>
{{/with}}

Rerun Helper Functions on Route Change for Active Links

I've been playing around with Meteor + Iron Router for a multi-page app I'm working on and I'm getting stuck on helper functions for named yields. Specifically, I've been trying to get the active class for my navbar tabs to update on each route change.
Below is the relevant code for my project:
router.js
Router.configure({
layoutTemplate: 'mothership',
yieldTemplates: {
'header' : {to: 'header'},
'footer': {to: 'footer'}
},
});
Router.map(function () {
// Home page
this.route('home', {
path: '/',
template: 'home',
});
this.route('about', {
path: '/about',
template: 'about',
});
this.route('emails', {
path: '/emails',
template: 'emails',
});
this.route('people', {
path: '/people',
template: 'people',
});
});
mothership.html
<template name="mothership">
Skip to content
<div id="wrap">
<!-- header -->
<div>{{yield 'header'}}</div>
<div id="content">{{yield}}</div>
</div>
<div id="push"></div>
<div id="footer">
{{yield 'footer'}}
</div>
</template>
header.html
...bootstrap stuff...
<a class="navbar-brand" href="{{pathFor 'home'}}">Mailchacho</a>
<li class="{{activeRoute 'about'}}">About</li>
<li class="{{activeRoute 'emails'}}">Sent Emails</li>
<li class="{{activeRoute 'people'}}">People</li>
...bootstrap stuff...
header.js
Handlebars.registerHelper('activeRoute', function(name) {
var active = location.pathname === Router.path(name);
return (active) ? 'active' : '';
});
// I know I can use Template.headers.helpers... to do this as well, I just found the registerHelper to be cleaner.
When I load a page from scratch, the proper active class is assigned, but when a route is changed on-page, the active class doesn't update. Using breakpoints, I can see the 'activeRoute' function isn't called on a change.
What's interesting is if I add a data dictionary to router.js, it does update. My guess is having the data dictionary indicates that something has changed between routes, forcing a refresh. What I'd like to do is have this refresh occur without needing to pass a data dictionary.
Since Iron Router is still fairly new, I haven't been able to able to find much online. The closest I've found is this issue on github (https://github.com/EventedMind/iron-router/issues/103), but the last comment was never resolved, which seems to be similar to mine.
With the above in mind, is there any way I can signal for a helper function to be rerun on a route change without passing a dummy data dictionary? I was thinking that something like Deps.autorun might be needed, but that doesn't feel right. I'm still pretty new to Meteor & Iron Router so any help here would be appreciated. Thanks!
Yours is a common problem so Mike Fitzgerald has built a package just for this purpose:
https://atmosphere.meteor.com/package/iron-router-active
The given example is like:
<nav>
<ul>
<li class="{{ isActive 'dashboard' }}">...</li>
<li class="{{ isActive 'dashboard|root' }}">...</li>
<li class="{{ isActive 'users' 'on' }}">...</li>
<li class="{{ isActivePath 'products' }}">...</li>
</ul>
</nav>
andd works through handlebars helpers which are called isActive, isActivePath, isNotActive and isNotActivePath.
I use meteor add zimme:active-route now. It works with iron:router, kadira:flow-router and meteorhacks:flow-router.
Just two examples: Output active class:
<li class="{{isActiveRoute 'home'}}">...</li>
Custom class:
<li class="{{isActiveRoute 'home' class='is-selected'}}">...</li>
https://atmospherejs.com/zimme/active-route

Meteor js - how to hook into the 'rendered' event of a recursive template?

I've got a situation where I'm rendering a Handlebars partial recursively based on a Mongodb tree structure, something like this :
<template name='menu'>
<ul class='menu'>
{{#each topLevelChildren}}
{{>menu-item}}
{{/each}}
</ul>
</template>
<template name='menu-item'>
<li>{{name}}
{{#if listChildren.count}}
<ul>
{{#each listChildren}}
{{>menu-item}}
{{/each}}
</ul>
{{/if}}
</li>
</template>
where the mongodb docs look like this :
{ _id : ObjectId("525eb7245359090f41b65106"),
name : 'Foo',
children : [ ObjectId("525eb60c5359090f41b65104"), ObjectId("525eb6ca5359090f41b65105") ]
}
and listChildren just returns a cursor containing the full docs for each element in the children array of the parent.
I want to do a bit of jquery makeup on the rendered tree, but I can't seem to hook into the 'rendered' event for the entire tree, something like
Template.menu-completed.rendered = function(){
// console.log('done!');
}
Trying this
Template.menu.rendered = function(){
console.log($('menu li'));
}
Not only doesn't this return the right results (brackets filled with commas), it also freezes web inspector (but not the app...).
Any help would be much appreciated !
This is an interesting problem :)
In the current version of Meteor (0.7.x) the rendered callback is called once when the template is first rendered, and then again once any partial inside the template is re-rendered. (This behavior will change when Meteor 0.8 - the shark branch or "Meteor UI" - lands.)
I've never tried doing things recursively, but it's going to be very hard to tell which callback you get is actually for the parent. One thing you might want to try is to start the recursive rendering in a template called menu-parent or something. That way, when the rendered callback is called for the first time (and as long as your data is loaded), you know the whole tree is rendered.
However, I suspect there might be a better way to do this. Generally, you should not be using jQuery to modify classes and attributes on DOM elements, as you'll confuse yourself as to what Meteor is doing and what jQuery is doing. Can you elaborate on what you are trying to achieve with the following, and I'll update my answer?
I need to find a specific anchor tag within the fully rendered template and add a class to it... and then add a class to each of its parents
Petrov, your code actually works for me, with just some minor modification to the jquery part. But perhaps I'm misunderstanding. Here is what I did:
Created a brand new project.
Added your template in the .html with minor change: <li><span class="name">{{name}}</span>
Created a new collection List.
Template.menu.topLevelChildren = function() { return List.find(); };
Then I added this handler:
Template.menu.rendered = function(e){
$('.name').each(function(i, e) {
console.log(e);
});
}
Now, upon render, for instance when new data is inserted into the List collection, I get a printout on the console like this:
<span class=​"name">​second​</span>​
<span class=​"name">​second2​</span>​
<span class=​"name">​second21​</span>​
<span class=​"name">​second22​</span>​
<span class=​"name">​third​</span>​
<span class=​"name">​third2​</span>​
<span class=​"name">​third21​</span>​
<span class=​"name">​third22​</span>​
<span class=​"name">​third​</span>​
<span class=​"name">​third2​</span>​
<span class=​"name">​third21​</span>​
<span class=​"name">​third22​</span>​
This is just a flat list of the tree elements I had added (where second2 is nested in second, and second2x is nested in second2, etc.). So from what I understand you could just take the selection coming back from jquery and find the tag you are looking for and change its class, as you wanted. Please let me know if I misunderstood something.
Complete code below.
test.html:
<head>
<title>test</title>
</head>
<body>
{{> menu }}
</body>
<template name='menu'>
<ul class='menu'>
{{#each topLevelChildren}}
{{>menu-item}}
{{/each}}
</ul>
</template>
<template name='menu-item'>
<li><span class="name">{{name}}</span>
<ul>
{{#each children}}
{{>menu-item}}
{{/each}}
</ul>
</li>
</template>
test.js:
List = new Meteor.Collection("List");
if (Meteor.isClient) {
Template.menu.topLevelChildren = function() {
return List.find();
};
Template.menu.rendered = function(e){
$('.name').each(function(i, e) {
console.log(e);
});
}
}
if (Meteor.isServer) {
Meteor.startup(function () {
// code to run on server at startup
});
}

Resources