Meteor routing: how to extend RouterController? - meteor

I'm reading the book 'Discover meteor' and have a question about pagination(pagination chapter).
I have a code in my router.js:
//router.js
...
PostsListController = RouteController.extend({
template: 'postsList',
increment: 4,
postsLimit: function() {
return parseInt(this.params.postsLimit) || this.increment;
},
findOptions: function() {
return {sort: {submitted: -1}, limit: this.postsLimit()};
},
subscriptions: function() {
this.postsSub = Meteor.subscribe('posts', this.findOptions());
},
posts: function() {
return Posts.find({}, this.findOptions());
},
data: function() {
var hasMore = this.posts().count() === this.postsLimit();
var nextPath = this.route.path({postsLimit: this.postsLimit() + this.increment});
return {
posts: this.posts(),
ready: this.postsSub.ready,
nextPath: hasMore ? nextPath : null
};
}
});
...
Router.route('/:postsLimit?', {
name: 'postsList'
});
And this working fine. My problem description:
I have another route ('/news') and whant to make pagination for this route too. How i should properly extend PostsListController to make it?
Every my post have a tag option, in this case it is a 'news', so i want to see only posts with 'news' tag.
I'm tryed to just copy-paste this controller(PostsListController) and:
renamed it;
set another template;
changed:
posts: function() {
return Posts.find({}, this.findOptions());
}
to:
posts: function() {
return Posts.find({postType: 'news'}, this.findOptions());
}
It not working, on my /page news i can see only all my news articles and spinner. I'm added:
Router.route('/news/:postsLimit?', {
name: 'newsTemplate',
controller: NewsTemplateController
});
But when i'm goind to /news/1 i'm see all my posts(not only one) and button 'show more'.
I think this copy-paste approach so bad but i have not ideas how to make it working proper way.

Your first issue where /news shows all the posts is because your first Route specification is too generic.
Router.route('/:postsLimit?', {
name: 'postsList'
});
This route specification will send all requests with one parameter to the PostsListController
ie. all of these paths will route to the PostsListsController:
/asdf
/test
/news
To fix this, you might want to make the first route more specific:
Router.route('/posts/:postsLimit?', {
name: 'postsList'
});
I am not sure why you are getting more than one item when going to /news/1.
Can you post your code for that controller?

Related

Inserting Blank item to database then redirecting to my item view route causes duplicate template elements in DOM

Let's say I have two routes.
One route is a "new Item" route and one is a "view Item" route.
In order to create a blank form I was just inserting a "blank" object into the database and then redirecting to the item view.
I understand this may not be the best way to do this, and I'm open to suggestions because I don't find a lot of tutorials that deal with this paradigm. Most just have a separate input form (which I don't want).
So the problem I'm running into is that when I navigate to my /new route, it ends up duplicating all the template content in the dom for my /org route even though it's just getting redirected to my /org route. I can reload the /org route all day with different "org data" and it never duplicates anything in my DOM. But the instant I hit that /new route button, it will add another set of DOM elements in duplicate fashion.
Can someone help me shed some light on what's going on? Here's my /new route code...
Router.route('/new/', {
name: 'new',
action: function() {
var newId = Organizations.insert({
// Set some defaults
init_date: new Date(),
modified_date: new Date()
});
this.redirect('/org/' + newId);
}
});
Here's my /org route code:
Router.route('/org/:_id', {
name: 'org',
subscriptions: function() {
return [
Meteor.subscribe('contacts', this.params._id),
Meteor.subscribe('organization', this.params._id)
];
},
onBeforeAction: function() {
user = Meteor.user();
if (!Roles.userIsInRole(user, ['admin'])) {
this.redirect('/submit-ticket');
this.stop();
} else {
this.next();
}
},
action: function() {
if (this.ready()) {
Session.set('currentOrgId', this.params._id);
this.layout('appLayout');
this.render('orgHead', {
to: 'orghead',
data: function() {
return Organizations.findOne({
_id: this.params._id
});
}
});
this.render('orgInfo', {
to: 'content',
data: function() {
return Organizations.findOne({
_id: this.params._id
});
}
});
} else {
this.layout('appLayout');
this.render('loading', {
to: 'content'
});
}
}
});

Iron Router OnBefore Hook with Params URL Not Calling Function

I am looking to restrict access to a page using iron router, but the before function doesn't seem to be running.
# Limit which challenges can seen to members.
isMemberHook = () ->
challenge = Challenges.findOne _id: #params._id
if Meteor.userId() in challenge.members
#next()
else
#redirect '/'
Router.onBeforeAction isMemberHook,
only: ['/challenge/:_id']
Turns out that for routes with "/" you need to use ".".
So in this case I used:
only: ["challenge.:_id"]
Problem solved!
You have to be careful to account for when a user is logged in, not logged in, and when they are logging in.
The following is code for a requireLogin function that I place in a onBeforeAction hook.
If there's no Meteor user, check to see if they're logging in, and if so, render a loading template.
var requireLogin = function() {
if (! Meteor.user()) {
if (Meteor.loggingIn()) {
this.render(this.loadingTemplate);
} else {
this.render('AdminLogin');
}
} else {
this.next();
}
};
Router.onBeforeAction(requireLogin, {only: ['AdminMain', 'postSubmit', 'appointmentsList', 'AppointmentShow']});
EDIT
Ok, this is what's happening:
https://github.com/iron-meteor/iron-router/blob/devel/Guide.md#applying-hooks-to-specific-routes
What's not perfectly clear from the documentation is what kind of arguments the array takes in the onBeforeAction
Router.onBeforeAction(requireLogin, {only: ['routeName', 'anotherRouteName']});
Here's a set of routes that work with the hook:
this.route('/order', {
template: 'order',
name: 'order',
layoutTemplate: 'layout'
});
this.route('/order/:_id', {
template: 'order',
name: 'orderEdit',
layoutTemplate: 'layout',
data: function(){
var id = this.params._id;
alert(id);
}
});
var beforeHook = function(){
console.log("beforeHook run!");
alert("safsf");
};
Router.onBeforeAction(beforeHook, {only: ['orderEdit']});

Meteor collection lookup on router level

I'm building an app that has versions of pages. I'd like users to be able to visit the page without having to specify the version they wish to start working with. In case no version is specified in the URL, I'd like to look up the latest version of that page and redirect to it.
The problem I'm having is that I can't do any collection lookups on the router level to figure out what version I'm looking for because the collections simply aren't there.
Here's my code so far:
Router.route('page', {
path: '/page/:slug/:version?',
onBeforeAction: function() {
var versionId = this.params.version;
if (!versionId) {
console.log('no version! oh noes!');
var p = Pages.find({slug: this.params.slug});
var newestVersion = Versions.findOne({page: p._id}, {sort: {createdAt: -1}});
versionId = newestVersion._id;
Router.redirect('page', this.params.slug, versionId);
}
this.next();
},
waitOn: function() {
return Meteor.subscribe('page', this.params.slug, this.params.state);
},
data: function() {
return {
page: Pages.findOne({slug: this.params.slug}),
version: Versions.findOne(this.params.version)
}
}
});
Any help or insights are very much appreciated!
Here's the solution I found. The problem is that the onBeforeAction didn't actually wait for the waitOn unless you tell it to.
onBeforeAction: function() {
if (this.ready()) {
if (!this.params.version) {
var p = Pages.findOne({slug: this.params.slug}),
sortedVersions = _.sortBy(Versions.find({page: p._id}).fetch(), function(s) {
return v.createdAt;
});
var newest_version_id = sortedVersions[0]._id;
this.redirect('page', {slug: this.params.slug, state: newest_version_id});
}
this.next();
}
}
I think the key is in the this.ready() check which is only true when all subscriptions in the waitOn are true. I assumed all this happened automagically by simply using the waitOn.

Spiderable and Iron-Router example

Does someone uses Iron-Router and Spiderable-404?
Please I need some complete examples to show me how to add the metatags.
They say this:
if (Meteor.isClient) {
Meteor.Router.add({
'/': 'index',
'/a': 'a',
'/b': function() {
Spiderable.httpHeaders['X-Foo'] = 'bar';
return 'b';
},
'*': function() {
Spiderable.httpStatusCode = 404;
return 'notFound';
}
});
}
but this does not work in iron router and there is no meta tag in this poor example, or I could not make it work, please help me fill the blanks.
The spiderable package is just a package you install and forget in Meteor. You do not use it directly from within your app.
Meteor SEO is not very mature, but you have options to get basic SEO working. Especially when you are using iron-router, there is the ms-seo smart package that you can use as:
$ mrt add ms-seo
and then in your router configuration:
Router.map(function() {
return this.route('blogPost', {
path: '/blog/:slug',
waitOn: function() {
return [Meteor.subscribe('postFull', this.params.slug)];
},
data: function() {
var post;
post = Posts.findOne({
slug: this.params.slug
});
return {
post: post
};
},
onAfterAction: function() {
var post;
// The SEO object is only available on the client.
// Return if you define your routes on the server, too.
if (!Meteor.isClient) {
return;
}
post = this.data().post;
SEO.set({
title: post.title,
meta: {
'description': post.description
},
og: {
'title': post.title,
'description': post.description
}
});
}
});
});
Also for 404 responses, iron-router's standard notFoundTemplate already provides the HTTP status code out of box. For other status codes, you need to define server side routes
````
Router.map(function () {
this.route('serverFile', {
where: 'server',
path: '/files/:filename',
action: function () {
var filename = this.params.filename;
this.response.writeHead(200, {'Content-Type': 'text/html'});
this.response.end('hello from server');
}
});
});
````
PS: You may want to refer to this blog post, and this one for further reading.
PPS: Your router configuration looks generally wrong. Your code looks like it belongs to the outdated meteor-router package. Refer to the iron-router package docs for more information.

Do Meteor-Roles and Iron-Router play nicely together?

I have a Meteor app with an editor page that should only be accessible to editors. I am using Iron-Router and my Router.map looks like the following. However, this is not working in an odd way. If I provide a link to the editor page, then all is well, but if I try entering the /editor url, then it always redirects to home, even if the user role is correctly set.
(One thing I ruled out was if Meteor.userId() is not set before Roles.userIsInRole is called in before.)
Anyone know why this would be?
Router.map(function() {
...
this.route('editor', {
path: '/editor',
waitOn: function() {
//handle subscriptions
},
data: function() {
//handle data
},
before: function() {
if ( !Roles.userIsInRole(Meteor.userId(), 'editor') ) {
this.redirect('home');
}
}
});
...
});
The Roles package sets up an automatic publication that sends the roles property on the Meteor.users collection. Unfortunately, you can't get a subscription handle for automatic publications, so you'll need to make your own.
Set up a new subscription that publishes the required data of a user, then configure Router to check that the data is ready before showing any page.
eg:
if (Meteor.isServer) {
Meteor.publish("user", function() {
return Meteor.users.find({
_id: this.userId
}, {
fields: {
roles: true
}
});
});
}
if (Meteor.isClient) {
var userData = Meteor.subscribe("user");
Router.before(function() {
if (Meteor.userId() == null) {
this.redirect('login');
return;
}
if (!userData.ready()) {
this.render('logingInLoading');
this.stop();
return;
}
this.next(); // Needed for iron:router v1+
}, {
// be sure to exclude the pages where you don't want this check!
except: ['register', 'login', 'reset-password']
});
}

Resources