Multiple Dynamic Segments in a Single Resource in Ember.js - javascript

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');
}
});

Related

How can I have optional segments in a route?

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() {
});
});
});

Using switch statements instead of using different functions in Backbone routing

Backbone routing allows us to route to different pages.
var Workspace = Backbone.Router.extend({
routes: {
"help": "help", // #help
"search/:query": "search", // #search/kiwis
"search/:query/p:page": "search" // #search/kiwis/p7
},
help: function() {
...
},
search: function(query, page) {
...
}
});
My question is instead of writing different functions for different routes, why not write a single function for all the routes and use a switch statement to determine the exact route and performing tasks based on the route.
It would look something like this.
var Workspace = Backbone.Router.extend({
routes: {
"help": "main", // #help
"search/:query": "main", // #search/kiwis
"search/:query/p:page": "main" // #search/kiwis/p7
},
main: function() {
...
switch(){
case("help") : ...;
case("search") : ...;
}
}
});
I don't know the exact implementation. I just gave a brief idea. Is this possible in Backbone routing?
Because that will lead to a nightmare hell as soon as you have more than 2 o 3 routes/functions, or you need anything more that 2 lines to setup the data and views for each route.
Also, it's much much easier to test your route handlers if you can simply call one function.
If you need one function per your requirements, then what's wrong is your route definition! I assume you are modeling a single page with search functionality and pagination of those search results. Let's suppose that page is accesed with a url like "yourapp/#page":
Enter optional parameters my friend: :)
http://backbonejs.org/#Router-routes
var Workspace = Backbone.Router.extend({
routes: {
"page(/search/:query)(/:page)": "main"
},
main: function(query, page) {
if(query) {
//you're searching
if(page) {
//display specific page
}
else {
//show first results page
}
}
else {
//show you initial views/models
}
}
});
That route will handle: page, page/search/apples and page/search/apples/4

How to migrate from meteor router to IR (Iron Router)?

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);
}
});

Ember.js afterModel hook triggers two identical requests

in my app I need that when I visit the root, it redirects to the view of the most recent model that in this case is always the firstObject in the collection.
App.Router.map(function() {
this.resource('threads', { path: '/' }, function() {
this.route('view', { path: ':thread_id' });
});
});
App.ThreadsRoute = Ember.Route.extend({
model: function() {
return this.store.find('thread');
},
afterModel: function(threads) {
this.transitionTo('threads.view', threads.get('firstObject'));
}
});
This is working without problems, but wheter I directly go to the root url or the view one 2 identical requests to /threads are made. As soon I comment the afterModel section the redirection obviously doesn't work anymore but the requests are back to 1.
Any help is gladly accepted!
Since Threads/View are nested routes, the ThreadsRoute will be also called on the View route.
I think you should just call the ThreadsRoute -> ThreadsIndexRoute or separate model and afterModel hooks this way:
(Not tested code)
App.ThreadsRoute = Ember.Route.extend({
model: function() {
// console.log('in model);
return this.store.find('thread');
}
});
App.ThreadsIndexRoute = Ember.Route.extend({
afterModel: function(threads) {
// console.log('in afterModel);
this.transitionTo('threads.view', threads.get('firstObject'));
}
});
Your example is identical to this one:
App.Router.map(function() {
this.resource('threads', { path: '/' }, function() {
this.route('view', { path: ':thread_id' });
});
});
App.ThreadsRoute = Ember.Route.extend({
model: function() {
return this.store.find('thread');
}
});
App.ThreadsIndexRoute = Ember.Route.extend({
model: function() {
return this.store.find('thread');
}
});
If you check the inspector for which route you're in when you visit '/', you'll see that you're inside of
threads.index, having transitioned into each of them in turn, which is why you're seeing the call to find twice.
You can fix this by only having the model hook in ThreadsIndexRoute (e.g. rename your ThreadsRoute to ThreadsIndexRoute)

Backbone router with multiple parameters

I need to get this to work:
routes: {
':product' : 'showProduct',
':product/:detail': 'showProductDetail'
showProductDetail never gets called while the ':product' route is set even if it is set afterwards. I tried the following
routes: {
':product(/:detail)': showProductOrDetail
}
But this will not get called when only the second parameter changes.
It is important that I have the product itself or the product and detail in the url.
Does anyone know how to fix this?
There's a little hacky solution to your problem. I have a feeling there is a nicer way to do this but that should work:
routes: {
"product/:id": "showProduct",
"product/:id/details/:did": "showDetails"
},
showProduct: function(id) {
this.showDetails(id);
},
showDetails: function(id, did) {
// Check did for undefined
}
A late response (over a year).. but you can use RegEx in a backbone router to achieve this.
My example presumes the parameters are going to start with a number.
ie: localhost:8888/#root/1param/2param
var router = Backbone.Router.extend({
initialize: function () {
// Use REGEX to get multiple parameters
this.route(/root/, 'page0');
this.route(/root\/(\d+\S+)/, 'page1');
this.route(/root\/(\d+\S+)\/(\d+\S+)/, 'page2');
},
page0:function(){
console.log("no id");
},
page1:function(id1){
console.log(id1);
},
page2:function(id1,id2){
console.log(id1);
console.log(id2);
}
});
Hope this helps.

Categories