Ember, working with a model - ember.js

I'm confused about how to set up retrieve information from my (dynamic) model in Ember.js
Here is my model (works so far):
App.Router.map(function() {
    this.resource('calendar', { path: '/calendar/:currentMonth'});
});
App.CalendarRoute = Ember.Route.extend({
model: function (params) {
var obj = {
daysList: calendar.getDaysInMonth("2013", params.currentMonth),
currentMonth: params.currentMonth
};
return obj;
}
});
I just want to get back the 'currentMonth' attribute:
App.CalendarController = Ember.Controller.extend({
next: function() {
console.log(this.get('currentMonth'));
}
});
But I am getting an "undefined" error.
Do I have to explicitly declare my model (Ember.model.extend()) in order to get and set values?

There are some conventions that you might not be aware of in regards to setting a Model into a Controller.
In a Route, model can be any object or collection of objects you define. There is a huge deal of conventions that apply and for most cases, you don't have to specify anything as it uses the names of various objects to guide itself on building the query and a set the content of your controller, however, in your particular code, you are return obj as the model.
Ember provides a hook called setupController that will set this object into your controller's content property. Example:
App.CalendarRoute = Ember.Route.extend({
model: function (params) {
var obj = {
daysList: calendar.getDaysInMonth("2013", params.currentMonth),
currentMonth: params.currentMonth
};
return obj;
},
setupController: function(controller, model) {
// model in this case, should be the instance of your "obj" from "model" above
controller.set('content', model);
}
});
With that said, you should try console.log(this.get('content.currentMonth'));

Related

When is the model attached to the controller?

At what point is the model attached to the controller? And, is there a hook when the model is there?
Because I face the following problem of not being able to access the model to do some computations with the data in it:
App.ResultsRoute = Ember.Route.extend({
model: function(){
// LOCAL, blocking call!
return {messages : this.store.all('message').toArray()};
}
});
App.ResultsController = Ember.ObjectController.extend({
init : function(){
this._super();
console.log(this);
console.log(this.get('model'));
console.log(this.get('model.messages'));
this._someComputation();
},
_someComputation : function(){
this.get('model.messages').forEach(function(message){
//do something
}
}
});
This prints:
Class { [...] model: messages: Array[58]0: Class1: Class2: [...] Class57: Class length: 58}
null
null
So, basically I can see the model in the console but when accessing it programmatically in the init function it's not there (yet?).
Or is this is the wrong way (or place) to do some computations with the model data?
edit:
The controller of a child route can access the model, so this would work as a workaround:
App.ResultsIndexController = Ember.Controller.extend({
needs: "results",
parentController : Ember.computed.alias('controllers.results'),
init : function(){
console.log("ResultsIndexController created");
console.log(this.get('parentController').get('model'));
}
});
Prints:
ResultsIndexController created
{messages: Array[55]}
But I'd rather have it in the parent controller such that all children can access it.
it's attached during setupController which happens in the route, after init, when the model has been resolved. The code essentially looks like this:
App.ResultsRoute = Ember.Route.extend({
model: function(){
// LOCAL, blocking call!
return {messages : this.store.all('message').toArray()};
},
setupController: function(controller, model){
controller.set('model', model);
}
});
If you wanted you could do it during setupController, it's a fairly common practice to override it to add additional functionality.
App.ResultsRoute = Ember.Route.extend({
model: function(){
// LOCAL, blocking call!
return {messages : this.store.all('message').toArray()};
},
setupController: function(controller, model){
this._super(controller, model);
controller._someComputation();
}
});
Additionally your model hook says it's a blocking call, but you aren't returning a promise, so that isn't true (but maybe that's just a copy paste foo)

Filtering RecordArray in a child route to return a RecordArray?

Im trying to my views change when a model property changes
Im not sure if I am on the right track or not, but here goes.
I have a parent resource called lunches, whose route looks like so:
model: function(params){
return this.store.filter('lunch', params, this.funcAll);
}
and I have a child route 'served', whose route looks like so:
model: function(params){
return this.modelFor('lunches').filterProperty('isServed', true);
// as opposed to this.store.filter('lunch', this.funcFilter()})
}
when I use filter in the child routes, I am able to do this (I think because a filter returns a RecordArray), but this does not work for me, for another reason. Is there anyway I can have my child route return a RecordArray which is a subset of the original model?
filter works on a collection.
App.ColorsRoute = Ember.Route.extend({
model: function() {
this.store.find('color');
return this.store.filter('color',function(){
return true;
});
}
});
App.ColorRoute = Ember.Route.extend({
model: function(params) {
return this.store.find('color', params.id);
},
setupController: function(controller, model){
this._super(controller, model);
var cMod = this.modelFor('colors'),
subItems = cMod.filter(function(item){
return item.get('color').indexOf('e')>=0;
});
console.log(subItems.get('length'));
}
});
http://emberjs.jsbin.com/OxIDiVU/858/edit

How to avoid too many empty records?

Ember : 1.5.0-beta.2
Ember Data : 1.0.0-beta.7
I have the following router:
App.Router.map(function() {
this.resource('posts', function() {
this.route('new');
});
});
My PostsNewRoute creates a new record in the model hook:
App.PostsNewRoute = Ember.Route.extend({
model: function() {
return this.store.createRecord('post');
}
});
Since I don't want transient record to be visible, I filter them out in my PostsRoute:
App.PostsRoute = Ember.Route.extend({
model: function() {
this.store.find('post');
return this.store.filter('post', function(post) {
return !post.get('isNew');
});
}
});
This works as expected, but every transition to posts.new add a new record to the store, which is something I would like to avoid. So, instead of calling createRecord every time the model hook is called, I filter the store for an empty record and return this if there is one found:
App.PostsNewRoute = Ember.Route.extend({
model: function() {
var route = this;
return this.store.filter('post', function(post) {
return post.get('isNew');
}).then(function(result) {
return result.get('firstObject') || route.store.createRecord('post');
);
});
This gives me at the most one empty record.
My question: is there a better way to avoid my store being populated with (many) empty records ?
UPDATE:
Instead of filtering on the isNew attribute, I can probably use currentModel:
model: function() {
this.get('currentModel') || this.store.createRecord('post');
};
You can use this addon https://github.com/dockyard/ember-data-route to clean up when you leave a /new route. It hooks into the willTransition action hook that gets called on the route whenever a transition occurs.
The source code is a short read: https://github.com/dockyard/ember-data-route/blob/master/addon/mixins/data-route.js.
The alternative would be to not create a new record in the model hook, but according to a comment of yours it doesn't seem to be an option.

Custom Dynamic Segment (NOT ID) - EmberJS

I want to have a dynamic segment path in Ember without using the :id attribute
As per the Ember Guides, I'm using the serialize method to achieve this.
Heres my Router:
App.Router.map(function() {
this.resource("orders", function(){
this.resource('order', { path: ':order_sequence'}, function(){
this.route('edit');
})
});
});
And my Route:
var OrderRoute = Ember.Route.extend({
model: function(params) {
return this.store.find('order', params.order_sequence)
},
serialize: function(model) {
return { order_sequence: model.get('sequence') };
}
});
module.exports = OrderRoute;
However, my URL's still behave using the id attribute in the path instead of the sequence attribute..
Any ideas?
Is your browser caching something, because that's correct. Are you passing in the id instead of the sequence/model in any of your transitionTo/transitionToRoute/link-to?
Oh, you aren't talking about the slug in the url, nor the route, you are talking about the id of your model. You need to create a serializer for that particular model and override the primary key
App.OrderSerializer = DS.RESTSerializer.extend({
primaryKey: 'sequence'
});
The Fixture Adapter has a constraint on defining the id, but you can lazily get around it by extending the fixture adapter and overriding a single method
App.OrderAdapter = DS.FixtureAdapter.extend({
fixturesForType: function(type) {
if (type.FIXTURES) {
var fixtures = Ember.A(type.FIXTURES);
return fixtures.map(function(fixture){
// aka we massasge the data a bit here so the fixture adapter won't whine so much
fixture.id = fixture.sequence;
var fixtureIdType = typeof fixture.id;
if(fixtureIdType !== "number" && fixtureIdType !== "string"){
throw new Error(fmt('the id property must be defined as a number or string for fixture %#', [fixture]));
}
fixture.id = fixture.id + '';
return fixture;
});
}
return null;
},
});

getting parameter value from nested routes

I have my router set like :
this.resource('analytics', {path: '/analytics'}, function(){
this.resource('analyticsRuns', {path: ':exerciseRunId/analyticsRuns'},function(){
this.resource('analyticsRun',{path: ':runId'});
});
});
I jump to 'analyticsRuns' route using :
this.transitionToRoute('analyticsRuns',{"exerciseRunId":this.get('selectedExerciseRun.id')});
And my AnalyticsRunsIndexRoute is defined as :
AS.AnalyticsRunsIndexRoute = Ember.Route.extend({
model : function(params) {
var store = this.get('store');
//console.log(params); //returns empty object
//var exerciseRunId = AS.Analytics.get('exerciseRunId');
exerciseRunId = 577;
if(!(exerciseRunId)){
this.transitionTo('analytics');
}
store.find('analyticsRun',{'exerciseRunId':exerciseRunId});
return store.filter('analyticsRun', function(analyticRun){
return analyticRun.get('exerciseRunId') == exerciseRunId;
});
},
setupController : function(controller,model){
this._super(controller,model);
this.controllerFor('analysisTemplates').set('model',controller.get('store').find('analysisTemplate'));
}
});
I was wondering if I could access ":exerciseRunId" value in the AnalyticsRunsIndexRoute. Currently there isnothing set when I check the params arguments passed to this routes' model. On refresh however, the parameter becomes available to the AnalyticsRunRoute but only on refresh. So do I have to play with stateManagement to get the parameter value? or is there simpler way to access it. Thanks.
SOLUTION :
Again lots of thanks to Jeremy for walking through this. Here is how I have set up things now :
I defied routes like :
AS.AnalyticsRunsRoute = Ember.Route.extend({
model : function(params) {
return params;
}
});
AS.AnalyticsRunsIndexRoute = Ember.Route.extend({
model : function(params) {
var parentModel = this.modelFor('analyticsRuns');
var exerciseRunId = AS.Analytics.get('exerciseRunId')||parentModel.exerciseRunId;
var store = this.get('store');
if(!(exerciseRunId)){
this.transitionTo('analytics');
}
store.find('analyticsRun',{'exerciseRunId':exerciseRunId});
return store.filter('analyticsRun', function(analyticRun){
return analyticRun.get('exerciseRunId') == exerciseRunId;
});
},
setupController : function(controller,model){
this._super(controller,model);
this.controllerFor('analysisTemplates').set('model',controller.get('store').find('analysisTemplate'));
}
});
When calling transitionToRoute you should be passing a live object.
this.transitionToRoute('analyticsRuns',this.get('selectedExerciseRun'));
When you transition from route to route the model hook is skipped so it's important that you pass live objects either in transitionToRoute or in a link-to.
[UPDATE] in response to a comment:
If selectedExcerciseRun is not a live object, then you'd need to instantiate a live object before transitioning. Something like this :
var runId = this.get('selectedExerciseRun.id');
var promise = store.find('analyticsRun',{'exerciseRunId':runId});
promise.then(function(analyticsRun){
this.transitionToRoute('analyticsRun',analyticsRun);
});