I am adding a new page to a website, and I am copying the code that already exists and is currently working in the website. Why is the FlowRouter.getParam coming up undefined when it works everywhere else?
client/JobInvoice.js
import { Invoices } from '../../../imports/api/Invoice/Invoice';
Template.InvoicePage.onCreated(function(){
const user = FlowRouter.getParam('_id');
console.log(user);
this.subscribe('invoices', user);
});
lib/router.js
Accounts.onLogout(function(){
FlowRouter.go('home');
});
FlowRouter.notFound = {
action: function() {
FlowRouter.go('/404');
}
};
const loggedIn = FlowRouter.group({
prefix: '/secure'
});
loggedIn.route( '/invoice', {
name: 'invoice',
action() {
BlazeLayout.render('FullWithHeader', {main:
'InvoicePage'});
}
});
What am I missing?
FlowRouter allows you to define routes with dynamic attributes (path-to-regexp), which are often representing document ids or other dynamic attributes.
For example
FlowRouter.route('/invoice/:docId', { ... })
would define a route that matches a pattern like /invoice/9a23bf3uiui3big and you usually use it to render templates for single documents.
Now if you want to access the document id as param docId inside the corresponding Template you would use FlowRouter.getParam('docId') and it would return for the above route 9a23bf3uiui3big.
Since your route definitions lacks a dynamic property, there is no param to be received by FlowRouter.getParam.
A possible fix would be
loggedIn.route( '/invoice/:_id', {
name: 'invoice',
action() {
BlazeLayout.render('FullWithHeader', {main:
'InvoicePage'});
}
});
to access it the same way you do for the other templates.
Readings
https://github.com/kadirahq/flow-router#flowroutergetparamparamname
Here is what I ended up doing and it works.
loggedIn.route( '/invoice/:id', {
name: 'invoice',
action() {
BlazeLayout.render('FullWithHeader', {main: 'InvoicePage'});
}
});
Related
I am working on a site where I have to search in the DB for string that come after the / on the root domain. I can't find anything about it in the documentation.
I am trying to make it work with Iron Router but any other suggestion would work out.
Thanks for the help!
Edit: Basically I just want to pass anything that comes after domain.com/ to a variable.
Here's something i've been doing so maybe it'll lead you down the right path
Route sends URL params to ownedGroupList template
Router.route('/users/:_id/groups', {
name: 'owned.group.list',
template: 'ownedGroupList',
data: function() {
return {params: this.params};
}
});
Template ownedGroupList can access params object using this.data in onCreated, onRendered, and onDestroyed template event handlers
Template.ownedGroupList.onCreated(function(){
this.subscribe("owned-groups", this.data.params._id );
});
Template ownedGroupList can access params through this variable in helper methods
Template.ownedGroupList.helpers({
groups: function() {
return Groups.find({owner: this.params._id });
}
});
Template ownedGroupList can access params through template.data variable in event handlers
Template.ownedGroupList.events({
'click .a-button': function(event, template) {
var group = Groups.findOne({owner: template.data.params._id });
// do something with group
}
});
Here's a simple route that should do the trick
Router.route('/:keyword', {
name: 'keyword',
template: 'keywordTemplate',
data: function() {
return this.params.keyword;
}
});
This will pass the keyword as the data context to your template and then you can do whatever you want with it. Alternatively you can perform the search straight in the router (especially if you're passing the keyword to a subscription so that the search runs on the server). For example:
Router.route('/:keyword', {
name: 'keyword',
template: 'keywordTemplate',
waitOn: function(){
return Meteor.subscribe('keywordSearch',keyword);
},
data: function() {
return MyCollection.find();
}
});
This second pattern will send your keyword to a subscription named keywordSearch that will execute on the server. When that subscription is ready, the route's data function will run and the data context passed to your keywordTemplate will be whatever documents and fields have been made available in MyCollection.
I'm doing something wrong while setting Session variables and handling the Router
main.js:
Template.global.onCreated(function(){
Session.setDefault("musicFilteredCategory", "latest")
});
router.js:
Router.route("/music/:category?", {
name: "music",
template: "music",
beforeAction: function () {
var category = this.params.category;
Session.set("musicFilteredCategory", category);
}
});
but when I open page "/music/latin-radio" and I check Session.get("musicFilteredCategory") I get "latest" instead of "latin-radio"
later I changed Session.setDefault("musicFilteredCategory", "latest") to outside the Template.global.onCreated({}) and the result is still the same.
What should be the best practice to do this?
I also want to add this feature once this is fixed:
when the user goes to "/music" to be redirected to "/music/:defaultMusicCategory"
PS: I'm using Meteor 1.2.0.1 & Iron Router 1.0.9
As #Kyll pointed out I should use onBeforeAction for the function to run.
This solved part of my problem, but the categories were not being changed when accessing the different routes.
Here's what I had to do:
Router.route("/music/:category?", {
name: "music",
template: "music",
onBeforeAction: function () {
var category = this.params.category;
if (category !== "undefined") {
Session.set("musicFilteredCategory", category);
}
this.render("music");
}
});
This doesn't cover the route "/music" (without the slash) so I also had to add this route, I placed it before the code above
Router.route("/music", {
name: "music",
template: "music"
});
To resolve this I had to move the Session.setDefault() outside the templates scope as they were overriding the Session established on the router, so I had to put them inside a Meteor.startup function
Meteor.startup(function () {
Session.setDefault("musicFilteredCategory", "latest");
});
I'm creating a set of routes, for example
/ - should render home page template
/items - should items page template
/items/weeARXpqqTFQRg275 - should return item from MongoDB with given _id
This is example of what I'm trying to achieve
Router.route('items/:_id', function () {
var item = return myItems.find(:_id);
this.render(item);
});
[update - solved]
solved this by using Router.map on server side instead of Router.route
Router.map(function () {
this.route('post', {
path: 'items/:_id',
where: 'server',
action: function(){
var id = this.params._id;
var json = myItems.findOne({_id: id});
this.response.setHeader('Content-Type', 'application/json');
this.response.end(JSON.stringify(json, null, 2));
}
});
});
There are several problems with your code.
First, it seems you want to get the _id parameter from the url and don't know how. It's stored on this.params, so it's this.params._id.
Second, the first parameter you should send to find is a MongoDB query that in your case would be { _id: this.params._id }.
Third, that's not how you render something in Iron Router. The string parameter on the render method is the name of the template you want to render, not the item.
Assuming that myItems is a valid collection and your template is called showItem, your code should be something like:
Router.route('items/:_id', {
name: 'showItem',
data: function () {
return myItems.find({ _id: this.params._id });
}
});
Try something like this:
Router.map(function () {
this.route('items/:myItemId', {
data: function(){
return myItems.findOne({_id: this.params.myItemId});
}
});
});
good luck!
I have an EmberJS application generated using ember-cli. I'm currently using simple-auth with a custom authenticator.
In the authenticator, when the user logs in I want to save his details so that I can use it later.
I have the following code:
authenticate: function(options) {
var self = this;
return new Ember.RSVP.Promise(function(resolve, reject){
API.user.login(options.username, options.password, true).done(function(data) {
// #TODO: Save current user
resolve(data.id);
}).fail(function() {
reject();
});
});
},
User data is available in the variable data.user.
I tried using Ember.set('App.currentUser', data.user); but it's not working. What should I do?
I think it works easiest to use an initializer. Theres several ways you can resolve the user, I think it is easiest if you pass the user_email alongside the grant token from the API
//initializers/session-user.js
import Ember from "ember";
import Session from "simple-auth/session";
export function initialize(container) {
Session.reopen({
setCurrentUser: function() {
var accessToken = this.get('access_token');
var self = this;
if (!Ember.isEmpty(accessToken)) {
return container.lookup('store:main').find('user', {
email: self.get('user_email')
}).then(function (users){
self.set('currentUser', users.get('firstObject'));
});
}
}.observes('access_token')
});
}
export default {
name: 'session-user',
before: 'simple-auth',
initialize: initialize
};
Check this thread for where the idea of this came from: http://discuss.emberjs.com/t/best-practice-for-loading-and-persisting-current-user-in-an-authenticated-system/6987
And if you are using simple-auth > 0.8.0-beta.1 you will need to adjust the initializer
I ended up creating a custom Sessions controller and setting the current user object there, and then creating an alias from the application controller.
Something like what's in this article.
I'm trying to make a publishment statement to publish
ONLY the author(OP)'s profile avatar. I am thinking of grabbing the _id of the page. And from that page, I will grab the userId which is the author's _id and try to show the profile.
However, I have been very unsuccessful, and currently, I am using the following. Publishing EVERY user's profile avatar.
Publications.js
//Need to filter this to show only OP.
Meteor.publish("userPostAvatar", function() {
return Meteor.users.find( {} ,
{
fields: {'profile.avatar': 1}
})
});
Meteor.publish('singlePost', function(id) {
check(id, String);
return Posts.find(id);
});
Router.js
Router.route('/posts/:_id', {
name: 'postPage',
waitOn: function() {
return [
Meteor.subscribe('singlePost', this.params._id),
Meteor.subscribe('userStatus'),
Meteor.subscribe('userPostAvatar')
];
},
data: function() {
return Posts.findOne({_id:this.params._id});
}
});
You can do a simple join in the userPostAvatar publish function like this:
Meteor.publish('userPostAvatar', function(postId) {
check(postId, String);
var post = Posts.findOne(postId);
return Meteor.users.find(post.authorId, {fields: {profile: 1}});
});
This assumes posts have an authorId field - adjust as needed for your use case. Note three important things:
You will need to subscribe with this.params._id just as you did for singlePost.
The join is non-reactive. If the author changes, the avatar will not be republished. Given the general nature of posts I assume this isn't a problem.
I didn't publish the nested field profile.avatar on purpose because doing so can cause weird behavior on the client. See this question for more details.
I believe you can achieve this within the iron:router data context, by finding the post, associated author (whatever the field is), and then the subsequent user avatar. You can return an object to the iron:router data context. Then you can access post and avatar in the template as variables (so you might need to adjust the template output a little).
Publications.js
Meteor.publish("userPostAvatar", function() {
return Meteor.users.findOne( {} ,
{
fields: {'profile.avatar': 1}
})
});
Meteor.publish('singlePost', function(id) {
check(id, String);
return Posts.find(id);
});
Router.js
Router.route('/posts/:_id', {
name: 'postPage',
waitOn: function() {
return [
Meteor.subscribe('singlePost', this.params._id),
Meteor.subscribe('userStatus'),
Meteor.subscribe('userPostAvatar')
];
},
data: function() {
var post = Posts.findOne({_id: this.params._id});
var avatar = Users.findOne(post.authorId).profile.avatar;
return {
post: post,
avatar: avatar
};
}
});
Two problems with this method are that you could achieve the same thing with template helpers, and the user publication hasn't been limited to one user (I'm unsure how to do this unless we know the authorId within the waitOn, although maybe you could try moving the logic to there instead of the data context as my example shows).