Meteor collection lookup on router level - meteor

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.

Related

Meteor publication not working

I have Iron Router and quite simple pub/sub.
When publication just returns some specific item - everything works fine. But when it does some logic inside (looping thru another collection) - it doesn't work (Iron Router's loading template keeps showing forever, and looks like no data is coming thru DDP from this publication).
The code of pub:
Meteor.publish('ordersWithState', function(orderState) {
// if uncommented, this line works just fine
//return Orders.find({name:"C02336"});
var temp = Workflows.findOne({name:"CustomerOrder"});
if (temp) {
var stateUuid;
_.each(temp.state, function (state) {
if (state.name == orderState) {
return Orders.find({stateUuid: state.uuid});
}
});
}
});
Router config (if needed):
this.route('ordersList', {
path: '/orders/list/:orderState?',
loadingTemplate: 'loading',
waitOn: function() {
console.log("in ordersList waitOn");
var orderState = this.params.orderState || "Требуется закупка";
return [
Meteor.subscribe('ordersWithState', orderState),
Meteor.subscribe('allSuppliersSub'),
Meteor.subscribe('tempCol'),
Meteor.subscribe('workflows')
];
},
data: function () {
return Orders.find({});
},
onBeforeAction: function (pause) {
this.next();
}
});
The problem is with the logic of your publication here:
if (temp) {
var stateUuid;
_.each(temp.state, function (state) {
if (state.name == orderState) {
return Orders.find({stateUuid: state.uuid});
}
});
}
You are returning something from your inner _.each function, but you are not returning anything from the publication function. So the publication is not returning anything to Iron Router or responding with this.ready();.
It is not exactly clear to me what you want to publish - an array of cursors or perhaps an Orders.find() with an $in: [arrayOfItems]? In any case Iron Router should work fine once the publication is fixed.

iron router syntax: onAfterAction

Router.route('/courses/:_catalog', function () {
var courseCatalog = this.params._catalog.toUpperCase();
Meteor.subscribe("courseCatalog", courseCatalog);
this.render('CourseDetail', {
to: 'content',
data: function () {
return Courses.findOne({catalog: courseCatalog});
}
});
}, {
onAfterAction: function() {
if (!Meteor.isClient) {
return;
}
debugger
var course = this.data(); <======
SEO.set({
title: "course.catalog"
});
}
});
In the above code, please look at the debugger statement. I want to access the data but it seems I am doing something wrong because this.data doesn't exist. I also tried Courses.find().fetch() but I only get an empty array inside onAfterAction. What's the right syntax and what am I missing?
It needs to be inside a this.ready() block:
onAfterAction: function() {
if (this.ready()) {
var course = this.data();
...
}
}
You need to subscribe to data first. Have a look at the waitOn function to do this. The server only sends the documents you subscribed to, and since you didn't subscribe, Courses.find().fetch() returns an empty array.
Also, don't put SEO stuff in onAfterAction. Put it in onRun which is guaranteed to only run once.

Why is data set with Meteor Iron Router not available in the template rendered callback?

This is a bit puzzling to me. I set data in the router (which I'm using very simply intentionally at this stage of my project), as follows :
Router.route('/groups/:_id',function() {
this.render('groupPage', {
data : function() {
return Groups.findOne({_id : this.params._id});
}
}, { sort : {time: -1} } );
});
The data you would expect, is now available in the template helpers, but if I have a look at 'this' in the rendered function its null
Template.groupPage.rendered = function() {
console.log(this);
};
I'd love to understand why (presuming its an expected result), or If its something I'm doing / not doing that causes this?
From my experience, this isn't uncommon. Below is how I handle it in my routes.
From what I understand, the template gets rendered client-side while the client is subscribing, so the null is actually what data is available.
Once the client recieves data from the subscription (server), it is added to the collection which causes the template to re-render.
Below is the pattern I use for routes. Notice the if(!this.ready()) return;
which handles the no data situation.
Router.route('landing', {
path: '/b/:b/:brandId/:template',
onAfterAction: function() {
if (this.title) document.title = this.title;
},
data: function() {
if(!this.ready()) return;
var brand = Brands.findOne(this.params.brandId);
if (!brand) return false;
this.title = brand.title;
return brand;
},
waitOn: function() {
return [
Meteor.subscribe('landingPageByBrandId', this.params.brandId),
Meteor.subscribe('myProfile'), // For verification
];
},
});
Issue
I was experiencing this myself today. I believe that there is a race condition between the Template.rendered callback and the iron router data function. I have since raised a question as an IronRouter issue on github to deal with the core issue.
In the meantime, workarounds:
Option 1: Wrap your code in a window.setTimeout()
Template.groupPage.rendered = function() {
var data_context = this.data;
window.setTimeout(function() {
console.log(data_context);
}, 100);
};
Option 2: Wrap your code in a this.autorun()
Template.groupPage.rendered = function() {
var data_context = this.data;
this.autorun(function() {
console.log(data_context);
});
};
Note: in this option, the function will run every time that the template's data context changes! The autorun will be destroyed along with the template though, unlike Tracker.autorun calls.

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