How can I have optional segments in a route? - javascript

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

Related

Emberjs making 2 simultaneous requests

My router.js file is like this:
this.route('cards', function() {
this.route('all');
this.route('card', {path: '/:card_id'}, function() {
this.route('edit');
});
this.route('new');
});
In all.js, I've:
model() {
return this.store.findAll('card');
}
In cards.js, I've:
beforeModel() {
this.transitionTo('cards.all');
},
model() {
return this.store.findAll('card');
}
As you can see that I'm making 2 requests which, IMO, is not necessary. So, if I remove the call from cards.js, the new.js doesn't work properly.
When I create a new card from new.js, after creation, it should go to /cards/1 and show the proper data. But, when I remove that line from cards.js, after creation of a card, it goes to /cards/1 but the data is not saved.
Link to repo: https://github.com/ghoshnirmalya/hub-client

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

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)

Multiple Dynamic Segments in a Single Resource in Ember.js

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

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