"Dynamic segment" in Ember.js? - ember.js

Throughout the Ember.js documentation, one finds the concept of dynamic segment mentioned at several places. What does it mean?

Updating with a proper sample: Demo | Source
Edit due to questions in comments:
In Ember, think of the Router mechanism as a State Machine: Each Route can be seen as a State. Sometimes tho, a state can have it's own little state machine within it. With that said: A resource is a state which you have possible child states. A PersonRoute can be defined as either as a resource our a route in the <Application>.Router.map callback; it really depends on your end goal. For example, if we think of a resource for a list of people based on a person model, we would potentially have a route to list all records.
App.Router.map(function() {
this.resource('people');
});
With this map, I'm telling my app that it needs a people template (and maybe a view), a people controller and a people route. A resource is also assumed to have a index route, which is implied and you don't have to code it, but if you need to, it would be PeopleIndexRoute, after the name of the resource itself, by convention.
Now I can (a) create a person route under people resource to be a single state of a person record; or (b) I can create a person resource under the people resource, so I would have more options under person resource (edit, detail, delete); or (c) I could create a separate resource for person, and use the path to override the url if I want to.
I sometimes go for option c:
App.Router.map(function() {
this.resource('people');
this.resource('person', {path: 'person/:person_id'}, function() {
this.route('edit');
this.route('delete');
});
});
That would make sense that edit is route since it doesn't have child states , only siblings (delete) and a parent (person). The url for a record would be something like this: ~/#/person/3/edit).
The routes, when not defined as a resource, won't have any child route/state, so you don't have person.edit.index like you have person.index, in other words, routes don't have child, only siblings and resources can have both.
Right now, the Routing Guide is the most solid piece of documentation we have about this. I strongly recommend it.
Dynamic Segment is a part of a route URL which changes according to the resource in use. Consider the following:
App.Router.map(function() {
this.resource('products', function() {
this.route('product', { path: ':product_id' })
}
});
In the stub above, the line:
this.resource('products', function() {
will produce the url
~/#/products
and the following line will produce
~/#/products/:product_id
replacing the dynamic part, you could have an url like this
~/#/products/3
the :product_id is what makes this route dynamic. The router will serialize the id of a resource (for example a Product model) to the URL and it also uses that id to find the a model in your DS.Store. You'll often see this in routes like the following:
App.ProductRoute = Em.Route.extend({
model: function(params) {
return App.Product.find(params.product_id);
}
});
So for this example, if you access ~/#/products/3, the app will then try to load an instance of the Product model from your store or try to fetch from your backend API.
You can see a fiddle that illustrates that here | source here
I also recommend this screencast by Tom Dale where he builds a blog reader app with Ember.js using the router and the ember-data API to load blog records based on the dynamic part of the URL.

Related

subdirectories within ember controller not working

from this post it seems that sub directories within ember controller should work.
https://github.com/ember-cli/ember-cli/issues/1219
however its not working for me .
here is my code branch like (directory cm contains a child directory views):
/controllers/cm/views/item.js
/routes/cm/views/item.js
/templates/cm/views/item.js
when i try to populate the model in route using the code below i see the data but when i put the same code in controller it never gets executed.
model: function(){
return this.store.find('item',{id: "1"});
}
entry in router.js is as follows:
this.resource('cm', {path: '/cm/:id'} , function() {
this.route('views');
this.route('views.items', {path: '/views/items'});
});
Clearly ember is not able to resolve the controller correctly.
Not sure how to fix this ...
That its becuase the model hook in a route works differently than in the controller.
in a route it is a method that can return a promise, the route will wait for the promise to be resolved before setting up the controller.
in the controller, it is just an attribute, which won't get executed until you getit, and even then, all you will get its a promise.
Wat?! Subdirectories work just fine. First, I'm not sure it's the best idea to use views or items as route names, as they're both very generic, and also used in some of the ember internals and can confuse things. Declaring a model called View could very well even break things in your app.
The controller/route/template structures for your router.js will be as follows:
<controllers|routes|templates>/cm.js
<controllers|routes|templates>/cm/index.js
<controllers|routes|templates>/cm/views.js
And I'm not sure what views.items would look like, because this would probably be better suited to making views a resource instead, or using a dash in the name, in which case the route declaration would be this.route('views-items', {path: '/views/items'});
Overall, I think your router definition should look like so:
this.resource('cms', function() {
this.resource('cm', {path: '/:cm_id'}, function() {
this.route('views');
this.route('views-items', { path: '/views/items' });
});
});
This isn't meant to be a quip--I'm here to help--but I think you need to spend a little more time with Ember's routing documentation to understand the conventions that Ember is expecting for certain router definitions. Also, the ember inspector tool is a really great asset when debugging router issues: https://chrome.google.com/webstore/detail/ember-inspector/bmdblncegkenkacieihfhpjfppoconhi

In Ember, multiple URLs, same route/controller/view/logic, different filter?

In Ember, I want the following urls to be handled by the same route:
/due: Show all tasks with due dates.
/due-past: Show all tasks with due dates in the past.
/due-today: Show all tasks with due dates today.
/due-tomorrow: Show all tasks with due dates tomorrow.
/due-this-week: Show all tasks with due dates this week.
/due-next-week: Show all tasks with due dates next week.
/due-this-month: Show all tasks with due dates this month.
I was imagining I would catch all urls by the same route and use the "when" part ('past', 'today', 'tomorrow', etc.) to filter in the model on that route. But I'm lost and don't know where to look for answers.
For example:
app/router.js:
Router.map(function() {
this.route('due', { path: '/due-:when' });
});
But this doesn't work.
Maybe I'm confused and don't understand Ember concepts. What is the Ember way?
Update
Now that I understand that the dynamic part must be a segment, I am forced to do something more like this:
Router.map(function () {
this.route('due');
this.route('due', {path: '/due/:when'});
});
But I realized that {{#link-to 'due'}}Due{{/link-to}} was just hitting the /due/undefined url, so /due is never getting hit. I guess I ditch the due route and just have /due/all? Oy. Every minute another compromise.
You could create a due route with nested routes for the period. URLs would then look something like this:
/due
/due/:period
In the due period you would fetch all tasks and in the period-routes you would have access to the period params in the params-parameter in the route's model-hook.
See http://emberjs.com/guides/routing/defining-your-routes/#toc_dynamic-segments for more detailed information.
The Ember way I suppose or the way I would implement something like this is to use the new query-params feature and declare the :period-dynamic segment a query params property on the due-Controller. With this you only end up with one route that is able to filter the due-tasks.
You can also specify if you want to query the server when a period changes or if you only want to filter the already fetched due-tasks considering that you would need to fetch all the tasks for the /due-route anyway.
If there was some kind of pagination in the due-route you would need to query the server for the different periods of course. See Opting into full transitions for details.
I think query params is the way to go here because the period routes are not really nested resources and will likely display the same interface as the due-route. The only thing that differentiates the two 'kind' of routes (due and due-period) is the filtering. Thus I'd opt for the query-params approach here.
If those are the routes you want, then there's no reason why you can't have them that way. This router map should get you that layout:
App.Router.map(function() {
this.resource('due'),
this.resource('past-due'),
this.resource('due-today'),
this.resource('due-tomorrow'),
this.resource('due-this-week'),
this.resource('due-next-week'),
this.resource('due-this-month')
});
Though, I'd probably go for something like this:
App.Router.map(function() {
this.resource('due', function() {
this.resource('today'),
this.resource('tomorrow'),
this.resource('this-week'),
this.resource('next-week'),
this.resource('this-month')
});
this.resource('past-due');
});
Then your routes would be:
#/due
#/due/today
#/due/tomorrow
#/due/this-week
#/due/next-week
#/due/this-month
#/past-due
It doesn't really matter too much if you use route or resource, its suggested that you use resource for top level items and routes for verbs like new or edit.
When it comes time to fetch your models, you'll just have to hardcode the values to retrieve, like this:
App.PastDueRoute = Ember.Route.extend({
model:function() {
this.store.find('todo',{due:'past-due'});
}
});
And you can link to routes by doing:
{{#link-to 'past-due'}}Past Due{{/link-to}}
If you don't have it already, I highly suggest that you install Ember Inspector in Chrome and use it too look at your app, specially your routes. It'll really help you get a sense of what you're defining.

Ember Routing: Reusing a resource under multiple parent routes

I have the following routing setup:
this.resource('blog',function(){
this.resource('selectimage',{path:"selectimage/:returncontext"},function(){});
});
this.resource('post',{path:":post_id"},function(){
this.resource('selectimage',{path:"selectimage/:returncontext"},function(){});
});
What I would have expected is, when navigating to blog.selectimage and post.selectimage, that the same router,controller and view would be used.
Unfortunately it seems that the last entry wins.
Is it possible to achievie this, without leaving the parent context(post/blog), when navigating to selectimage
(Obviously I could inherit from the base-selectimages-classes, but I'm hoping there is some generic ember way of doing this.)
You cannot have same resource name for two different routes as Ember relies more on naming conventions. But Ember provides ways to reuse the same controller and templates
Rename your selectimage under post to something else
this.resource('post',{path:":post_id"},function(){
this.resource('xxx',{path:"xxx/:returncontext"});
});
And in the router, you could specify the templateName and controller that has to be reused. Something like this should make it reusable
App.xxxRoute = Em.Route.extend({
controllerName: 'selectimage',
templateName: 'selectimage'
});
Sample Demo

Passing parameters between routes

What is the "appropriate" way in Ember to send a parameter from one route to another? For instance, I have two routes defined as such:
this.resource('activities', { path: '/activities/:on_date' }, function() {
this.route('new');
});
when on the ActivitiesRoute the user is presented with a dropdown of possible activities. When they choose something it transitions to the ActivitiesNewRoute:
this.transitionToRoute('activities.new');
and I know there is a second parameter available in the transitionToRoute(route,model) method but it's meant for passing in a model and I'm assuming this shouldn't be repurposed for other parameter passing. In this case the dropdown choice is picking an Action model id and the model for ActivitiesNew is a Activity.
Here are my three guesses at ways that might work:
1) Make it a router parameter
I supposed I could change ActivitiesNew to include a "parameter" as part of the route:
this.route('new', { path: '/new/:my_parameter' });
I'm not sure I'd really like to have it becoming part of the URL path but if this was the prevailing convention then I'd live with that.
2) Get a handle, post transition
Immediately following the transitionToRoute call I could set a property of the new controller class. Not sure if the controller would be setup yet but I'm imagining something like:
this.transitionToRoute('activities.new');
this.get('target').controllerFor('activities.new').set('my_parameter', myValue);
3) Use model parameter
this.transitionToRoute('activities.new',myValue);
I suspect that this is a major no-no. I haven't looked into the Ember code to know if this could work but it seems against convention so this is my "bad option".
transitionTo & transitionToRoute return a "promise-like" object. The parameter this object is resolved with is the route, from which you can access controller and currentModel. So a nice clean way to pass information to a route to which you are transitioning is:
var my_param = ....;
this.transitionToRoute('activities.new').then(function(newRoute) {
newRoute.currentModel.set('someProperty', my_param);
//or
newRoute.controller.set('someProperty', my_param);
});
EDIT/RANT:
note that in most cases, you do want to use needs, and bind things between controllers. However, there are certainly instances when you have things that depend on the logic of a route transition -- eg., controllerB has state X if we came to routeA from routeB, but state Y if we came from routeC. In that case, my answer is valuable.
The primary value of stack overflow to the development community is not the immediate answers you get to questions you post, but the massive ever growing wealth of googleable development knowledge. When you "infer" from a user's question that they "should" be doing something other than what they are asking how to do, you may be right (or you may be just incapable of imagining their particular circumstance), but if you answer only with your recommendation/rule/aphorism/cargo-cult-dictum instead of answering the ACTUAL QUESTION, you diminish the value of everybody else's google searches. If you want to tell someone to do something other than what they're asking, do it in a comment, or in a footnote to an answer to the actual question.
You can use the needs API (Read about it here):
App.ActivitiesNewController = Ember.ObjectController.extend({
needs: ['activities']
// Bind the property you need
actionTemplateBinding: 'controllers.activities.actionTemplate'
});
So what you actually need is to pass a parameter between controllers, which is exactly what needs is for. Plus, binding the property with needs you ensure it is in sync at all times, instead of relying on setupController being called.
You could use query-params (http://guides.emberjs.com/v1.10.0/routing/query-params/), as follows:
this.transitionToRoute('activities.new', {queryParams: {my_param: 'my_value'});
In order to be able to receive my_param in the new controller, you would also need to define the following lines:
App.ActivitiesNewController = Ember.ObjectController.extend({
queryParams: ['my_param'],
my_param: ''
...
});
A drawback of this solution is that the value of my_param will be serialized in URL - so it would not be suitable for some sensitive information you may want to pass between routes.
I'll answer my question with what I've decided to go with for now but keep it open for a a few days to see if anyone comes back with a more experienced answer. My answer may very well be perfectly fine ... it works anyway.
I've gone with a variation of #2 from the question. The difference is that that rather than trying to set a property in the ActivitiesNew controller from Activities controller I do the the opposite:
In ActivitiesNewRoute:
App.ActivitiesNewRoute = Ember.Route.extend({
model: function(params) {
return this.store.createRecord('activity');
},
setupController: function(controller,model) {
controller.set('actionTemplate', this.controllerFor('activities').get('actionTemplate'));
}
});
Still interested in hearing from people if there's a better way of doing this.
Transition to route with params and set model
yourAction:->
model = 'your-model'
route = 'your.path.to.toute'
routeLoad = #transitionToRoute route,
id: model.get 'id'
routeLoad.then (route) ->
route.set 'controller.model', model
return

Router v2.1 transitionTo does not work

I'm trying to use the new Router API (at commit 6a165ad), and I have some problems.
Given this router:
Router.map(function(match) {
match("/posts").to("posts", function(match) {
match("/new").to('new', function(match) {
match("/author").to('author');
});
});
});
I'm trying to transition to the new route.
Using new.index: this.transitionTo('new.index')
It works, but as you can see the route name is not really explicit (we don't even know that it's for a new post). It's consequently not a viable solution.
Using posts.new: this.transitionTo('posts.new')
I hoped it works, but an error is thrown:
The route posts.new was not found.
I believed the transition to the index was made automatically, but it seems not.
Using customized route name:
Since the commit specified above, Ember allows custom route naming. As my previous attempt does not work, I tried to force the new route to be posts.new, but it still does not work (idem if it was foo.new).
It looks like its not possible to go to a customized route that has nested routes.
TL;DR
I'd like to transition to the new route (and specifying posts). How should it be done ?
Before the router v2.1, I had routes that has child without to (i.e. match("/posts", function(match) { ... })), is it still working ? If so, what's the name of its children ?
This was actually a bug in Ember. Because index is implicit, you should not need to explicitly provide it.
The bug was fixed on master.
If you want to go to a route that has child routes, you should transitionTo the specified name of the route, and Ember will automatically add the index for you.