I have a few paths that look as follows on my application, all under a breadcrumb path.
/class/:slug
/class/:slug/student/:_id
/class/:slug/teacher/:_id
However, this tends to lead to redundancy because I have to constantly look up the class by slug before querying
this.route('Class.teacher', {
path: '/class/:slug/teacher/:_id',
waitOn: function() {
return Meteor.subscribe('teachers');
},
data: function() {
var classId = Classes.findOne({ slug: this.params.slug })._id;
if(!classId) {
throw new Meteor.Error(404, 'That class does not exist');
}
return Teacher.findOne(
{ _id: this.params._id, classIds: classId }
);
}
})
The problem is the amount of redundancy this appears to induce, as I start most of my routes with finding the class.
Is there a way to perform some sort of manipulation on the /class/:slug route? I was thinking something like this:
Router.route('/class/:slug/*', {
waitOn: function() {
return Meteor.subscribe('classes', this.params.slug);
},
onBeforeAction: function() {
var class = Classes.findOne(this.params.slug);
if(!class) {
throw new Meteor.Error(404, "Class not found");
this.stop();
}
this.next();
}
});
However, this doesn't work. What is the proper way to handle breadcrumb paths in this way?
You could probably use
Router.onBeforeAction(function(req, res, next){
// find class here
// req.params.slug Not sure about this
next()
}, { only: ['class'] });
Just an idea though.
Related
I have a use case and I’m not sure if there is an easy solution within the current Ember router or not, is there a way to define an optional routing param?
I would like to have a dynamic route that might have 1 or might have 2 segments, and then further routes nested inside. My idea of the structure would be something like the below (except the :topic part would be optional)
this.route('course', { path: '/:course' }, function() {
this.route('page', { path: '/:topic/:page' }, function() {
this.route('menu', function() {
});
});
});
/my-course/my-topic/my-page would hit the page route
/my-course/my-page would hit the page route
/my-course/my-page/menu would hit the menu route
/my-course/my-topic/my-page/menu would hit the menu route
One solution I have found is to use the wildcard and then break the segments down myself.
this.route('course', { path: '/:course' }, function() {
this.route('page', { path: '/*path_for_page' }, function() {
this.route('menu', function() {
});
});
});
New to meteor, I'm having a hell of a time with the whole client/server thing. Super fun.
I want to read a list of all files that are in a folder, and just print them on the UI, on a template. It has to be possible. Here's the code.
js file:
var availableFiles = [];
if (Meteor.isServer) {
var fs = Npm.require('fs');
availableFiles = fs.readdirSync(process.env.PWD + '/public/uploads');
console.log(availableFiles);
}
Router.map(function() {
this.route('home', {
path: '/',
template: 'home',
data: {files: availableFiles}
});
});
html file:
<template name="home">
{{#each data.files}}
{{this}}
{{/each}}
</template>
I've tried to put the fs calls inside a function on data on the route definition, but I get "Npm is not defined" errors. I don't know if that's how it should be structured or not, remember, newb here. Any help would be greatly appreciated.
I'd probably do this with a reactive array (http://reactivearray.meteor.com/) and a Meteor method. Something like:
if (Meteor.isServer) {
var fs = Npm.require('fs');
Meteor.methods({
getFiles() {
return fs.readdirSync(process.env.PWD + '/public/uploads');
}
});
}
if( Meteor.isClient ) {
Template.home.onCreated( function() {
Template.instance().files = new ReactiveArray();
Meteor.call('getFiles', function(error, result) {
if( error ) {
console.log( error );
} else {
Template.instance().files = files.concat( result );
}
});
});
Template.home.helpers({
files() {
return Template.instance().files.array();
}
});
}
Router.map(function() {
this.route('home', {
path: '/',
template: 'home',
data: {files: availableFiles}
});
});
Syntax might not be perfect, but that should get the point across. You'll also need to change your template to {{#each files}}
I am try to publish posts created by the usernames in the following array each users has. I am adding this to the microscope practice app The error is I20140826-20:31:53.452(-4)? at Meteor.publish.Comments.find.postId [as _handler] (app/server/publications.js:2:13). Thanks in advanced here is the code.
publications.js
The loop is supposed to publish posts made by each username in the following array.
Meteor.publish('posts', function(options) {
for (u=0;u<this.user.profile.following.length;u++) {
f=profile.following[u].text();
return Posts.find({username:f}, options);
}
});
The Routes it will affect
Controllers
PostsListController = RouteController.extend({
template: 'postsList',
increment: 5,
limit: function() {
return parseInt(this.params.postsLimit) || this.increment;
},
findOptions: function() {
return {sort: this.sort, limit: this.limit()};
},
waitOn: function() {
return Meteor.subscribe('posts', this.findOptions());
},
posts: function() {
return Posts.find({}, this.findOptions());
},
data: function() {
var hasMore = this.posts().count() === this.limit();
return {
posts: this.posts(),
nextPath: hasMore ? this.nextPath() : null
};
}
});
NewPostsListController = PostsListController.extend({
sort: {submitted: -1, _id: -1},
nextPath: function() {
return Router.routes.newPosts.path({postsLimit: this.limit() + this.increment})
}
});
BestPostsListController = PostsListController.extend({
sort: {votes: -1, submitted: -1, _id: -1},
nextPath: function() {
return Router.routes.bestPosts.path({postsLimit: this.limit() + this.increment})
}
});
Router map
this.route('newPosts', {
path: '/new/:postsLimit?',
controller: NewPostsListController
});
this.route('bestPosts', {
path: '/best/:postsLimit?',
controller: BestPostsListController
});
Your publish function is wrong, are you aware that your loop is useless because it is exiting upon encountering the first return ?
Even if you were aggregating the cursors accumulated in the loop this wouldn't work because at the moment a publish function can only return multiple cursors from DIFFERENT collections.
You need to use the appropriate mongo selector here, which is probably $in.
Also, profile.following is not even defined in the publish function, and iterating over an array is done by checking the iterator variable against the array length (profile.following.length), or better yet using Array.prototype.forEach or _.each.
I think this is what you're trying to do :
Meteor.publish("posts",function(options){
if(!this.userId){
this.ready();
return;
}
var currentUser=Meteor.users.findOne(this.userId);
return Posts.find({
username:{
$in:currentUser.profile.following
}
},options);
});
You should definitely read resources about JavaScript itself before digging any further in Meteor, if you're following the Discover Meteor book I think they provide some good JS tutorials for beginners.
Ref to the question Trying to Migrate to Iron-Router from Router. I still dont understand how to migrate meteor router to iron-router.
I am using router in my meteor project. The router file is like followings:
Meteor.Router.add({
"/settings": function() {
if (!Roles.userIsInRole(Meteor.user(), ['admin'])) {
return false;
}
return 'site_settings';
},
"/new_page": function() {
if (!Roles.userIsInRole(Meteor.user(), ['admin'])) {
return false;
}
return 'new_page';
},
"/navigation": function() {
if (!Roles.userIsInRole(Meteor.user(), ['admin'])) {
return false;
}
return 'navigation';
},
"/login": function() {
return 'loginButtonsFullPage';
},
"/users": function() {
if (!Roles.userIsInRole(Meteor.user(), ['admin'])) {
return false;
}
return 'admin_users';
}
});
If someone knows how to use an iron-router to replace the return template in the right way. Much appreciate.
I meet a little bit complicated router function, and I have no idea how to solve it. the code is like:
"/": function() {
// Don't render until we have our data
if (!GroundDB.ready()) {
//if (!Offline.subscriptionLoaded('pages') || !Offline.subscriptionLoaded('settings')) {
return 'loadingpage';
} else {
var page_slug = utils.getSetting('indexPage');
var page = Pages.findOne({slug: page_slug});
if(!page) {
page = Pages.findOne();
// if pages dont have any public pages
if (!page) {
var isIndexPageInNav=Navigation.findOne({"location":"header_active","pages.slug":page_slug});
// if index page slug in navigation that means the user dont have right to view this slides or the index page not exist
if(isIndexPageInNav)
return 'loginButtonsFullPage';
else
return '404';
}
else {
page_slug = page.slug;
}
}
Session.set("page-slug", page_slug);
return page.template;
}
}
As you know the iron-router need give a template at the begining. but with router I can return dynamic templates. How does iron-router implement this idea.
Router.map(function() {
//site_settings being the name of the template
this.route('site_settings', {
path: '/settings',
action: function() {
if (!Roles.userIsInRole(Meteor.user(), ['admin'])) {
//if the conditional fails render a access_denied template
this.render('access_denied');
} else {
//else continue normally rendering, in this case the 'site_settings'
//template
this.render();
}
}
});
this.route('loginButtonsFullPage', {
path: '/login'
});
});
Note since you will be doing that if user is admin conditional a lot you can wrap that logic inside a controller and link it to all the relevant routes such as:
Router.map(function() {
this.route('site_settings', {
path: '/settings',
controller: 'AdminController'
});
this.route('new_page', {
path: '/new_page',
controller: 'AdminController'
});
this.route('navigation', {
path: '/navigation',
controller: 'AdminController'
});
//etc...
//don't need to add the controller for this one
//since all users have access
this.route('loginHuttonsFullPage', {
path: '/login'
});
});
AdminController = RouteController.extend({
action: function() {
if (!Roles.userIsInRole(Meteor.user(), ['admin'])) {
this.render('access_denied');
} else {
this.render();
}
}
});
A couple of other things you will want to check out in iron-router are layouts with {{> yield}} and waitOn which is indispensable.
The docs at https://github.com/EventedMind/iron-router will do a better job of explaining those concepts then I can here.
Here is my attempt at your more complicated route. It may not work right away because I may be misunderstanding what you are doing but the key things are to substitute the returns with this.render(template_name); waitOn instead of checking if something is ready(), adding all the required subscriptions to the waitOn and then finally adding all your logic to an action
//note: index is the name of the route, you do not actually need a template called index.
//in the previous examples where no template to render was returned then iron-router will
//look for a template with the same name as the route but in this route we will be providing
//it with a specific route name in all cases
this.route('index', {
path: '/',
//assuming GroundDB is a subscription e.g. GroundDB = Meteor.subscribe('groundDB');
//I don't know what your page and nav subscriptions are called but you should wait on them too.
//if you haven't assigned them to a variable do something like
//pageSubscription = Meteor.subscribe('pages');
waitOn: [GroundDB, pageSubscription, navigationSub],
//the template to load while the subscriptions in waitOn aren't ready.
//note: this can be defined globally if your loading template will be the same
//for all pages
loadingTemplate: 'loadingpage',
//here we evaluate the logic on which page to load assuming everything has loaded
action: function() {
var page_slug = utils.getSetting('indexPage');
var page = Pages.findOne({slug: page_slug});
if (!page) {
var isIndexPageInNav = Navigation.findOne({"location":"header_active","pages.slug":page_slug});
if(isIndexPageInNav)
this.render('loginButtonsFullPage');
else
this.render('404');
} else {
page_slug = page.slug;
}
Session.set("page-slug", page_slug);
this.render(page.template);
}
});
Is there a way to have multiple dynamic segments with a single resource? My use case is to avoid letting the user hit index routes.
Example:
this.resource('tracks', { path: 'albums/:album_id/tracks/:tracks_id' });
And I'd like to avoid the user from hitting the following routes:
albums/:album_id
albums/:album_id/tracks
albums/:album_id/tracks/:track_id
Routes:
this.resource('albums', { path: 'albums' }, function(){
this.resource('album', { path: '/:album_id' }, function() {
this.resource('tracks', { path: 'tracks' }, function(){
this.resource('track', { path: '/:track_id' });
});
});
});
Any help would be greatly appreciated.
Defining Your Routes
NOTE: If you define a resource using this.resource and do not supply a
function, then the implicit resource.index route is not created.
It would be better to use Ember's nested routes. Each route having its own dynamic segment.
App.Router.map(function () {
this.resource('albums', { path: '/albums' }, function () {
this.resource('album', { path: ':album_id' }, function () {
this.resource('tracks', { path: 'tracks' }, function () {
this.resource('track', { path: ':track_id' });
});
});
});
});
If you want to show the user the first track immediately after clicking an album, you could use a redirect.
App.AlbumRoute = Ember.Route.extend({
afterModel: function (album, transition) {
this.transitionTo('track', {album_id: album.id, track_id: album.tracks[0].id});
},
});
Check out the docs on redirection: http://emberjs.com/guides/routing/redirection/
Just for completeness sake, the index routes aren't necessary, they are just a freebie convenience if you define them, if you don't define them it won't go to them.
http://emberjs.jsbin.com/eMofowUQ/1/edit
And you can define multiple slugs in a single path and go directly to it, just note you'll only have a single model for that single resource, so you'll have to deal with that.
http://emberjs.jsbin.com/eMofowUQ/2/edit
A possible solution for us was to use the following:
App.AlbumsIndexRoute = Ember.Route.extend({
redirect: function(){
this.transitionTo('dashboard');
}
});