In Ember rc6 I was successfully binding a controller JobsTableColumnsController to an attribute columns inside of a controller JobsTableController. The JobsTableColumnsController would be created and bound to the columns attribute automatically.
Here is the code that works in rc6:
App.JobsTableRoute = Ember.Route.extend
model: -> App.Job.all()
setupController: (ctlr, model) -> ctlr.set('content', model)
App.JobsTableController = App.TableController.extend
needs: ['jobsTableColumns']
columnsBinding: 'controllers.jobsTableColumns'
App.JobsTableColumnsController = App.ColumnsController.extend
content: Em.A([
App.ColumnDefinition.create(name: 'Id')
App.ColumnDefinition.create(name: 'Description')
])
In rc8 I have to explicitly set the JobsTableColumnsController to the JobsTableController.columns attribute in the router like so:
App.JobsTableRoute = Ember.Route.extend
model: -> App.Job.all()
setupController: (ctlr, model) ->
columns = #controllerFor('jobsTableColumns')
ctlr.set('columns', columns)
ctlr.set('content', model)
Is this a bug, or do I need to change my strategy of binding controllers to attributes using the needs attribute.
Is this a bug, or do I need to change my strategy of binding controllers to attributes using the needs attribute.
No it's not a bug, the use of somePropertyBinding was quietly deprecated in favor of computed properties. For reference please see the comment by Peter Wagenet here: https://github.com/emberjs/ember.js/issues/1164#issuecomment-23200023
And as for the new strategy you should use Ember.computed.alias.
Example:
App.JobsTableController = App.TableController.extend
needs: ['jobsTableColumns']
columns: Ember.computed.alias('controllers.jobsTableColumns')
This way you don't need the extra work in the JobsTableRoute setupController hook.
Hope it helps.
Related
I've got an ember-data model that has an id as well as a server-generated custom slug attribute (which is prefixed with the id if that matters).
I'd like to use the slug instead of the id in all public routres. For that purpose, I have changed the router:
App.Router.map ->
#resource 'strategies', path: '/strategies', ->
#resource 'strategy', path: ':strategy_slug'
and overwrote the serialize method of the respective route:
App.StrategyRoute = Ember.Route.extend()
serialize: (model) ->
{
strategy_slug: model.get('slug')
}
Unfortunately, this does not seem to work when using transitionToRoute from a controller to transition to, e.g., /strategies/123-test:
App.ExampleController = Ember.ObjectController.extend()
[...]
actions:
showDetails: ->
#transitionToRoute("/strategies/#{#get('slug')}")
false
(where #get('slug') returns '123-test')
All output I get in the console after invoking the showDetails action is:
Transitioned into 'strategies.index'
Ember does seem to recognize the slug-based route.
Ember: 1.5.0-beta.2+pre.3ce8f9ac
Ember Data: 1.0.0-beta.6
Is there anything I may have missed?
Edit:
The following variant works and is feasible in this case, but I have another use-case where I have only access to the URL.
App.ExampleController = Ember.ObjectController.extend()
[...]
actions:
showDetails: ->
#transitionToRoute('strategy', #get('content'))
false
I eventually figured out how to solve this. The key is to add a model method to the router, which knows how to turn a slug into a model:
App.StrategyRoute = Ember.Route.extend()
model: (params, transition) ->
App.store.getById('strategy', parseInt(params.strategy_slug))
serialize: (model) ->
{
strategy_slug: model.get('slug')
}
I have a route that loads all my models, and a nested route that allows the user to add a new model.
App.Router.map(function() {
this.resource("foo", {path: "/foo"}, function() {
this.route("add", {path: "/add"});
});
});
My add template looks like this (very basic)
{{input value=wat}}
Here is the linkTo from my index template
{{#linkTo 'foo.add'}}Add A New Model{{/linkTo}}
When I click the add button I simply create the model using $.ajax and transition back to the list route. All works great, until I click the "add" link again.
When the add route loads up the template from above the 2nd time it still shows the "wat" value I entered previously. I was hoping it would not persist any state as each time I "add" a new model it should be unaware of any previous model data.
How can I achieve this with ember 1.1.2+
Update
The approach I took was to reset each element in the setupController method of the route (as this is invoked each time you load the controller).
App.FooAddRoute = Ember.Route.extend({
model: function(params) {
var parentId = 1;
return Ember.Object.create({'bar': parentId});
},
setupController: function(controller, model) {
this._super(controller, model);
controller.set('bazz', '');
}
});
The quick and dirty answer is you want to use a model on the route. If you didn't, you'd have to manually blank out the values on the controller. Ember builds up singleton controllers. This generally is super convenient and very performant.
Singleton controllers keep state. The best way to keep them stateless is to have them backed by a model (return an empty object from the model hook, and don't have the values defined on the controller). By returning something from the model hook it will use an ObjectController (or you'll need to update your code to use an ObjectController on your controller). Then all values will be proxied to the model instead of being stored on the controller.
http://emberjs.jsbin.com/OPaguRU/1/edit
According to the docs:
If you're using Ember Data, you only need to override the model hook
if you need to return a model different from the record with the
provided ID
But this does not work for me, ember data gives me wrong data.
App.UsersEditRoute = Ember.Route.extend
model: (params) ->
return ['Just', 'Some', 'Random']
setupController: (controller, model) ->
controller.set('model', model) # Returns #get('store').find 'user', params.user_id
This should return ['Just', 'Some', Random], but instead it gives me the original #get('store').find 'user', params.user_id
Why and how do I get the data I want?
Btw, If I do like below, everything works, but I want to know why my model function never is called.
setupController: (controller, model) ->
controller.set('model', ['Just', 'Some', 'Random']) # returns ['Just', 'Some', 'Random']
Thank you, I'm using ember-data 0.14 and ember 1.0.0
The model hook, for routes with a dynamic segment, is only called when the page is (re)loaded, here's what the ember guide says (the note at the end):
Note: A route with a dynamic segment will only have its model hook called when it is entered via the URL. If the route is entered through a transition (e.g. when using the link-to Handlebars helper), then a model context is already provided and the hook is not executed. Routes without dynamic segments will always execute the model hook.
I had a similar problem when I wanted to override the model hook. The answer from Simon gave me the right direction. In addition, it should be noted, also from the ember guide but in the Links section, that the {{link-to}} helper takes:
At most one model for each dynamic segment. By default, Ember.js will
replace each segment with the value of the corresponding object's id
property. If there is no model to pass to the helper, you can provide
an explicit identifier value instead. The value will be filled into
the dynamic segment of the route, and will make sure that the model
hook is triggered.
So the bottom line is that by replacing the model in the {{link-to}} helper (in my case 'product') by the object id (in my case 'product.id'), my model hook is now called every time.
The router of my application looks like this (it's CoffeeScript):
App.Router.map () ->
#resource 'conversations', { path: '/' } ->
#resource 'conversation', { path: ':conversation_id' }
#route 'new'
So, in my app, I have paths like /new, /1, /2, etc.
I would like to detect a transition from /1 to /2 to make some initializations in my view (basically, put the focus on a textarea field). Unfortunately, as /1 and /2 use the same route, it seems nearly impossible to detect that transition.
I tried by using didInsertElement in the view (as described here) or by observing currentPath in the controller (as described here). It works fine if I go from /new to /1 (different routes) but not if I go from /1 to /2.
I found this gist suggesting to use the StateManager but it seems outdated (and I'm not sure it's really what I need).
What do you suggest me to do?
EDIT
It seems that setupController is called every time so I decided to overload it like this:
App.ConversationRoute = Ember.Route.extend {
setupController: (controller, model) ->
controller.set 'model', model
# do something here?
}
And I want the init method in my view to be called:
App.ConversationView = Ember.View.extend {
init: ->
#$('form textarea').focus()
}
But I still can't figure out how to make these two things work together (it's a problem because I read that the controller is not supposed to know about the view).
Thank you for your help!
Use the didInsertElement view hook and an observer.
App.ConversationView = Ember.View.extend
didInsertElement: ->
#focusOnTextarea()
modelChanged: (->
#focusOnTextarea()
).observes('controller.model')
focusOnTextarea: ->
#$('form textarea').focus()
In the case of going from /1 to /2, the route and view are not changing. Ember does the least amount of work possible. There's no need to re-render the view, so it doesn't. But this tripped me up too, and I think it's a big gotcha.
Also, if you override init in your view, make sure to call #_super().
Note: the model hook is only called when landing on a page to deserialize the URL, not when transitioning from another page and changing the model instance.
Route#model is your friend here. It will receive a params hash containing information from the URL on every route change (even when changing just which instance of a class is being viewed) In your case,
App.ConversationRoute = Ember.Route.extend {
model: (params) ->
App.Conversation.find params.conversation_id
setupController: (controller, conversation) ->
// you have the correct Conversation object
The guides have more examples.
The didInsertElement method of the view is the best method if you need to instantiate something on your view. If you need to have the controller do something when the template loads, you can put the call in the setupController method of your route:
App.FirstRoute = Ember.Route.extend({
setupController: function(controller){
controller.onload();
}
});
Here's a jsfiddle with a full example:
http://jsfiddle.net/jgillick/Q5Kbz/
This will be called each time that route is loaded. Try the jsfidle. Click along to the second template and then use your browser's back button. The onload should fire again.
Also, fun fact, you can use the deactivate method as an unload, to do anything you need to that controller when the user navigates away from that route:
App.FirstRoute = Ember.Route.extend({
deactivate: function(){
this.controller.unload();
}
});
Unrelated
One thing to note, (not directly related to your question) if you set the model on the controller in the setupController method, it will overwrite your controllers content property each time that route is loaded. To prevent this, put a conditional around the assignment:
setupController: function(controller, model) {
controller.onload();
if (model && (!controller.content || Ember.keys(controller.content).length === 0)) {
controller.set('content', model);
}
}
Is it considered Ember.js best practice to keep model interactions -- creation, say -- in the route, or the controller?
An example: the following CoffeeScript works fine, and also works if the 'save' logic is moved into a controller. Is one practice preferred over the other, and if so, why?
App.UsersNewRoute = Ember.Route.extend
model: ->
App.User.createRecord()
setupController: (controller, model) ->
controller.set('content', model)
events: {
save: (user) ->
user.on "didCreate", #, () ->
#transitionTo 'users.show', user
#get('store').commit()
}
In general, if an action affects state just in a particular controller, or the model that that controller fronts, then you should handle it in the controller. If it affects broader application state (i.e. another controller), or results in a route transition, or should be handled by different logic based on the state of the app, it should be handled in the router.