Iron Router / Meteor - Post details with username in URL - meteor

I'm relatively new to Meteor (especially Iron Router), and have been stuck on the following issue...
I have a route which displays details about a single post:
this.route('singlePost',{
path:'/posts/:_id',
data:function(){
return Posts.findOne(this.params._id);
}
});
This works fine, but I'd like to be able to show the post owner's username in the URL, rather than the static "/posts/" path, ex:
this.route('singlePost',{
path:'/:username/:_id',
data:function(){
return Posts.findOne(this.params._id);
}
});
The post object includes the user Id of the owner, but not the username (username is in the Meteor.users collection).
When I try to set the route with 2 dynamic values (username, post Id), the pathFor link disappears (I assume because it cannot find "username" in the post object that is returned).
How can I get the route to recognize the username? I assume some lookup function to the Users collection but I'm not sure when/where. Also, how would I be able to validate the route to make sure the post is owned by the correct username?
Edit - here is the code:
router.js
Router.configure({
layoutTemplate: 'layout',
loadingTemplate: 'loading',
waitOn:function(){
return Meteor.subscribe('posts') && Meteor.subscribe('users');
}
});
Router.map(function() {
this.route('home', {
path: '/',
data:function(){
Session.set('pageView','list');
return Posts.find();
}
});
this.route('singlePost',{
path:'/:username/:_id',
data:function(){
Session.set('pageView','single');
return Posts.findOne(this.params._id);
}
});
});
Router.onBeforeAction('loading');
home.html
<template name="home">
{{> postsList}}
</template>
posts_list.html
<template name="postsList">
<ul>
{{#each posts}}
{{> postBlock}}
{{/each}}
</ul>
</template>
single_post.html
<template name="singlePost">
{{> postBlock}}
</template>
post_block.html
<template name="postBlock">
{{#if pageView "list"}}
<li>
{{title}}<br/>
Author: {{username}}
</li>
{{/if}}
{{#if pageView "single"}}
<h1>{{title}}</h1>
<p>{{description}}</p>
<p>Author: {{username}}</p>
{{/if}}
</template>
post_block.js
Template.postBlock.helpers({
username:function(){
var user = getUserInfo(this.owner);
return user.username;
},
pageView:function(type){
return Session.get('pageView') == type;
}
});
functions.js
getUserInfo = function(id){
return Meteor.users.findOne(id);
}
The username outputs correctly on both the list and the details views, however I cannot get the pathFor link to include the username.

Looking at your template, you appear to be not passing username or id in {{pathFor 'singlePost'}}.
It should be {{pathFor 'singlePost' username=username _id=yourId}}
Your route should work then.

Related

How to find current template in meteor?

Is there any function to find current templates?
<template name="Login">
<h1>{{template}}</h1> //Should display Login
</template>
or
<template name="Login">
<h1>{{showtemplate}}</h1>
</template>
Helperjs:
showtemplate: function(){
return Template;
}
If you want the name of the template, you can get it like this:
showtemplate: function(){
return Template.instance().view.name;
}
This will return the string "Template.yourTemplateName"

'this' context in Meteor event is empty object

Template works fine (in terms of data being displayed), but event doesn't. Particularly odd because I have a different template with almost the identical code in which it works.
<template name="profile_sidebar">
{{#if opened}}
{{> profile_sidebar_contents}}
{{/if}}
</template>
<template name="profile_sidebar_contents">
{{#if dataReady}}
{{#unless equalsCurrentUsername profile.login}}
<span>
<a class="message-user"><i class="ion-chatbox"></i> Message</a>
</span>
{{/unless}}
{{/if}}
</template>
Template.profile_sidebar_contents.events({
'click .message-user': function(e,t){
// this is {}
// t.data is null
Session.set('selectedConversation', this._id);
Router.go('/messages');
}
});
Thank you!
Found a solution!
I wrapped the entire template in a {{#with profile}} ... {{/with}} block and then added the data I needed to be within the profile object returned in the helper. It seems as though the context of the event was empty object because the event target was not within a scope.
Elaborated below
I assumed that the context would default to an object which had as fields all helpers. Ex. I had
Template.profile_sidebar_contents.helpers({
profile: function(){ return something },
id: function() {return somethingelse }
});
and I expected the context of this to be {profile: something, id: somethingelse}
but it seems that this isn't done and the context is empty. I moved it to be
Template.profile_sidebar_contents.helpers({
profile: function(){ return {profile:something, id:somethingelse} }
});
and then set {{#with profile}} ... {{/with}} and had access to the profile helper returned object, by which I could retrieve id by this.id and profile by this.profile

Meteor.js Iron Routing :_id dynamic route confusion

I'm currently working my way though "Your Second Meteor Application" and have been enjoying it so far. Everything I have created works but I do not understand why the following works but the code at the end does not.
Template
<template name="list">
<ul>
{{#each list}}
<li>{{name}}</li>
{{/each}}
</ul>
</template>
<template name="listPage">
<h2>Tasks: {{name}}</h2>
</template>
Route
Router.route('/list/:_id', {
template: 'listPage',
data: function(){
var currentList = this.params._id;
return Lists.findOne({_id: currentList});
}
});
This is giving the expected results. However, I was curious why the following will not work as it seems to be passing the exact same thing. The only differences with the following are:
changing the Router.route('lists/:_id') to Router.route('lists/randomParm')
this.params._id to this.params.randomParm
Template
<template name="list">
<ul>
{{#each list}}
<li>{{name}}</li>
{{/each}}
</ul>
</template>
<template name="listPage">
<h2>Tasks: {{name}}</h2>
</template>
Route
Router.route('/list/randomParm', {
template: 'listPage',
data: function(){
var currentList = this.params.randomParm;
return Lists.findOne({_id: currentList});
}
});
The message I am getting is:
Oops, looks like there's no route on the client or the server for url: "http://localhost:3000/list/TGM9dbRRtspyJy7AR."
Isn't :_id and randomParm holding the same values? An id of list items from the HTML links that are being passed to the routing url and being used to make a mongo call? I don't quite understand how :_id and randomParm are different when I am hitting the same routing URL.
Param shold be with :
So your route will be
Router.route('/list/:randomParm', {
If this param is optional then leave ? after
Router.route('/list/:randomParm?', {

Rendering Template in Meteor and Iron Router depending on value in document

I am trying to render a template depending on a value of a field in a document.
I tried using a switch case in a helper but the return value comes out incorrect.
units_list.html
<template name="unitsList">
{{#each units}}
{{> unitItem}}
{{/each}}
</template>
units_list.js
Template.unitsList.helpers({
units: function() {
return Units.find({}, {sort: {name: 1}});
}
});
unit_item.html
<template name="unitItem">
{{name}}
</template>
unit_item.js
Template.unitItem.helpers({
unitType: function() {
var unitType = this.unitType;
switch(unitType){
case 'first': return "{{pathFor 'unitPageFirst'}}";
case 'second': return "{{pathFor 'unitPageSecond'}}";
}
}
});
I'm either going about this the wrong way or missing something elementary...
I've cut out a lot of code to focus on the problem.
Any ideas on how to get this working, or any suggestions on how to do it better?
You can't return uncompiled Spacebars strings from JS at execution time.
You can either use Router.path to get the path for your routes within your template helper :
Template.unitItem.helpers({
unitType: function() {
var unitType = this.unitType;
switch(unitType){
case 'first':
return Router.path('unitPageFirst', this);
case 'second':
return Router.path('unitPageSecond', this);
}
}
});
Or you can use plain Spacebars by declaring template helpers to check against the unitType.
HTML
<template name="unitItem">
{{#if unitTypeIs 'unitTypeFirst'}}
{{name}}
{{/if}}
{{#if unitTypeIs 'unitTypeSecond'}}
{{name}}
{{/if}}
</template>
JS
Template.unitItem.helpers({
unitTypeIs: function(unitType){
return this.unitType == unitType;
}
});
Have a look at Rendering Templates in the Iron-router guide, specifically the this.render('xyz'); statement
https://github.com/iron-meteor/iron-router/blob/devel/Guide.md#rendering-templates

How to deal with the situation that template is rendered but the data is not ready?

In client startup I subscribe to something:
Meteor.publish("Roles", function(){
return Roles.find();
});
Meteor.startup(function() {
if(Meteor.isClient) {
Meteor.subscribe('Roles');
}
});
And roles template:
Template.roles.helper(function() {
allRoles: function() {
return Roles.find().fetch();
}
})
<template name="roles">
<div>
{{#with allRoles}}
<label>{{> role }}</label>
</div>
</template>
The problem is sometime roles template is rendered before the Roles is ready.
How to deal with this situation?
You can do the subscribe on the template and then use the Template.subscriptionReady helper to create a conditional to show a loading panel whilst your subscription is being loaded as follows:
Template.roles.onCreated(function () {
this.subscribe("Roles");
});
Template.roles.helper(function() {
allRoles: function() {
return Roles.find().fetch();
}
})
<template name="roles">
<div>
{{#if Template.subscriptionsReady}}
{{#with allRoles}}
<label>{{> role }}</label>
{{else}}
Loading...
{{/if}}
</div>
</template>
This replaces your other subscription and these subscriptions can be added to each onCreated method for each template to have subscriptions per template.
There are some common ways of dealing with it. You can use a guard or make use of iron router's waitOn function. With a guard you only return data from the helper if you're getting any results:
allRoles: function() {
var roles = Roles.find();
//explicit version
if (roles.count()) return roles
//implicitly works as well here because you're returning null when there are no results
return roles
}
You don't need the fetch() in this case, because #with works with a cursor. If you run into a situation where you need to fetch first because you're returning partial data, check that there are results first and only then return them.
You can also use iron router's waitOn Option if you're using this as part of a route.

Resources