How to create simple userProfile route in Meteor with Iron Router - meteor

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}}

Related

Setting different routes dependent on if user logged in Meteor

I am trying to get my head around setting different master route templates for account holders and guests. I cannot seem to render different templates (two different nav bars) dependent on whether a user has logged in or not. I have set up a template for guests
//guest.js
<template name="unregistered">
{{>navUnreg}}
<div class="container">
{{>about}}
</div>
</template>
<template name="navUnreg">
<nav class="navbar navbar-default">
<div class="container">
<ul class="nav navbar-nav">
<a class="navbar-brand" href="#">CabStack</a>
</ul>
<ul class="nav navbar-nav navbar-right">
{{>loginButtons}}
</ul>
</div>
</nav>
</template>
This is the generic about page for users that are not logged on. I then have the template for Account users that have logged in which is -
//UserLoggedIn.js
<template name="cabMaster">
{{>cabNav}}
<div class="container">
{{>yield}}
</div>
</template>
I initially tried to place an if/else statement into a Router configure -
//config.js
Router.configure({
if(Meteor.user()){
layoutTemplate: "cabMaster"
}
else{
layoutTemplate: "unregistered"
}
});
However I keep getting thrown an error. I have already used the Router.onBeforeAction on the userLoggedIn.js page to restrict access to routes until a profile page is filled out which is
//Access rights for logged in users
function userNoAccess(){
if(!Meteor.userId()){
alert("restricted until profile page completed");
this.render(“profile”);
return pause();
} else {
this.next();
}
}
//Do not allow access to nav links until user has filled out ‘profile’ page.
Router.onBeforeAction(userNoAccess, {
only: ["dashboard", "edit_dashboard", "help"]
});
So
I did think about placing the userLoggedIn route in the Accounts.onLogin() function that I have in my client.js file but I want to keep all routes in one place. Does anyone have any suggestions as to how I might achieve this. Thanks
There are several ways to go about this. You don't have to do everything in the router for example. The choice of navbar can happen right in your template:
<template name="navBar">
{{#if currentUser}}
{{> navReg}}
{{else}}
{{> navUnreg}}
{{/if}}
</template>
This is quite convenient when you're trying to alter little parts of your layout without switching out the whole layout.
I suggest you look at using controllers for dealing with authenticated and unauthenticated routes. They make your routing code much more maintainable. I've written a basic tutorial that you might find helpful.

Why does route to home changes after rendering a template?

I am just getting started using the iron:router package. These are my project files:
router-example.js
if (Meteor.isClient) {
//some code
}
if (Meteor.isServer) {
//some code
}
Router.route('/', function(){
this.render('Home');
});
Router.route('/hello', function(){
this.render('hello');
});
Router.route('/items', function(){
this.render('Items');
});
Router.route('/serverItem', function(){
var req = this.request;
var res = this.response;
res.end('Hello from the server\n');
}, {where: 'server'});
router-example.html
<body>
<h1>Welcome to Meteor!</h1>
<ol>
<li>This routing doesn't work</li>
<li>Hello Template</li>
<li>Items Template</li>
<li>Server Item</li>
<li>Hard link works</li>
</ol>
</body>
templates.html
<template name = "Home">
<h2>Default: '/' brings you here</h2>
<p>This is the home template</p>
</template>
<template name = "Items">
<h2>This is the items template. Items Page linked using pathFor helper</h2>
</template>
<template name="hello">
<button>Click Me</button>
<p>You've pressed the button {{counter}} times.</p>
</template>
So at the home page "localhost:3000", the "Home" template is rendered by default, as expected. Once I click on the other links:
Hello Template,
Items Template etc.
Those are rendered, but home link specified using the {{pathFor '/'}} helper stops working and I have to use a hard link (localhost:3000) to get back to the home page. Hovering the mouse over that link shows that it's pointing to a different route.
So what am I doing wrong here?
You can specify route name in order to use {{pathFor 'routeName'}}:
Router.route('/', {
name: 'home',
template: 'Home'
})
Look here for full example https://github.com/iron-meteor/iron-router/blob/devel/Guide.md#route-specific-options
If no name is provided, the router guesses a name based on the path

Proper way to handle multiple "pages" in meteor

What is the "formal" way of handling multiple "pages" in meteor? I say "pages" I've seen people do it a couple of different ways. I've seen people create actual full pages, (index.html, about.html, contact.html) and then when links are clicked, you'd write a route to render those pages. But I've also seen people essentially put the code for each of those pages inside <template> tags and then do nifty show/hide type stuff based of what they've clicked, login credentials, etc.
There are several ways to handle multiple pages in meteor
1.iron-router
using iron router you can create a layout template and inject all other templates inside it using {{> yield}}. Check the iron-router guide to know more about layout template.
2.{{> Template.dynamic}}
if you do not want to add iron-router you can use {{> Template.dynamic}} to achieve the same.
<body>
<ul>
<li><a href="#" class="index">Home</li>
<li><a href="#" class="about">About</li>
<li><a href="#" class="contact">Contact</li>
</ul>
{{> Template.dynamic template=template_name }}
</body>
template_name can be changed reactively using a template helper
Meteor.startup(function () {
Session.setDefault("templateName", "index")
});
Template.body.helpers({
template_name: function(){
return Session.get("templateName")
}
});
Template.body.events({
"click .home": function() {
Session.set("templateName", "index");
},
"click .about": function() {
Session.set("templateName", "about");
}
// ..
});
If the Session returns "about" then,
{{> Template.dynamic template="about"}} which is equivalent to {{> about}}.

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>
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;
}
});

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

Resources