Controller support for nested model data - ember.js

I have nested model structure like this:
App.Survey = DS.Model.extend({
name: DS.attr('string'),
questions: DS.hasMany('question', {async: true})
});
App.Question = DS.Model.extend({
questionName: DS.attr('string'),
parentQuestionId: DS.attr('number'),
position: DS.attr('number'),
questionLayoutId: DS.attr('number'),
questionLayoutName: DS.attr('string'),
childQuestions: DS.hasMany('question', {async: true})
});
And I have itemController set up to help add extra "properties" to the model content by making controller like:
App.QuestionsController = Ember.ArrayController.extend({
itemController: 'question'
});
App.QuestionController = Ember.ObjectController.extend({
needs: 'questions',
questions: Ember.computed.alias("controllers.questions"),
editMode: false,
hasChildren: function () {
return (this.get('childQuestions.length') > 0);
}.property('childQuestions'),
isBlockQuestion: function () {
return this.get('questionLayoutName') == "layout-block"
}.property('questionLayoutName')
});
So when I go to the survey, I can see list of questions in the survey. My route is setup like this :
App.SurveyRoute = Ember.Route.extend({
model: function (params) {
return this.get('store').find('survey', params.survey_id);
},
setupController: function(controller, model){
this._super(controller, model);
this.controllerFor('questions').set('model', model.get('questions'));
}
});
Now with this setup, I have the power of item controller for only root level questions but not the child level questions. I am wondering if there is a way to bind model data to appropriate controller as need be.
Here is a JSbin to demonstrate my problem: http://jsbin.com/UROCObi/2/
It might be bit too much to go into, but the concept is pretty simple. A survey can have multiple questions and a question in itself can have child questions(which in my case are called block questions). As you can see I am not able to see 3rd level questions, because its not encapsulated in any controller. Do I need to instantiate ArrayController in SurveyRoute for childQuestion for all nested level or is there other cleaner way to do things?
Thanks,
Dee

You can use:
{{#each questions itemController="question"}}
...
{{#each childQuestions itemController="childQuestion"}}
...
{{/each}}
{{/each}}
And the context inside each each is an instance of a QuestionController and a ChildQuestioncontroller, respectevely (I'm not sure about the naming convention).
No need to use an ArrayController, unless you also need to control the array as a whole.

Related

EmberFire Adding relationship from different model

-------------------------------
Ember : 1.13.11
Ember Data : 1.13.15
Firebase : 2.3.2
EmberFire : 1.6.3
jQuery : 1.11.3
-------------------------------
I've got two endpoints in my firebase app. /employees and /subjects. In my ember app I want to add subjects to an employee (employees/$id/subjects). The problem is, I don't know how to load all my subjects from /subjects so I can add them to my array.
This is my routes:
Router.map(function() {
this.route('dashboard');
this.route('employees', function() {
this.route('employee', { path: '/:id'});
});
});
And this is my model
export default Ember.Route.extend({
model(params) {
return this.store.findRecord('employee', params.id);
}
});
I've tried various things to get this to work, creating a subroute this.route(employee, function(){ this.route('subjects') }, loading a second model in my employee model, none of which has worked. I'm new to ember so I might have gotten some things mixed up. Any helps is appreciated.
EDIT
Employee model
export default DS.Model.extend({
name: DS.attr('string'),
position: DS.attr('string'),
accessoryPosition: DS.attr('string'),
education: DS.attr('string'),
experience: DS.attr('string'),
imgUrl: DS.attr('string'),
subjects: DS.hasMany('subject', {async: true})
});
Subject Model
export default DS.Model.extend({
name: DS.attr('string')
});
To maybe describe my intentions a bit better, here is the flow I want:
User selects an employee, employees info is shown along with a list of subjects assigned to that employee. But I also need the full list of subjects available, if I want to assign a new subject to an employee. Hence my question. Hope this makes sense
Okay, then you can do this in your Route:
export default Ember.Route.extend({
model(params) {
return Ember.RSVP.hash({
employee: this.store.findRecord('employee', params.id),
subjects: this.store.findAll('subject')
});
}
});
then in your template:
{{#each employee.subjects as |subject|}}
{{!these are your employee subjects}}
{{subject.name}}
{{/each}}
{{#each subjects as |subject|}}
{{!these are all your subjects}}
{{subject.name}}
{{/each}}

Accessing a model from a controller in Ember

I have a pretty basic setup where I'm trying to format a date in my Controller. The problem is I can't access it in the formattedStart function below, whereas I CAN access it in the summaryRowAction handler. This is baffling me, because console.logging this in both places gives the same result. But for some reason inside of formattedStart, this.get('model.startDate') is undefined.
App.SummaryRowController = Ember.ObjectController.extend({
formattedStart: function(){
console.log(this.get('model.startDate');)
return this.get('model.startDate');
}.property(),
actions: {
summaryRowAction: function(){
console.log(this.get('model.startDate'));
}
}
});
Here is my model and my template (in Jade) for reference:
App.PricingSummary = DS.Model.extend({
startDate: DS.attr(),
endDate: DS.attr(),
days: DS.hasMany('day', {async: true}),
property: DS.belongsTo('property', {async: true})
});
script(type="text/x-handlebars", data-template-name="summaryRow")
.summaries__summary("{{action 'summaryRowAction'}}")
.summaries__summary--item{{formattedStart}} — {{endDate}}
It's because the first (and only) time that the property is evaluated, model is actually null. You need to specify startDate as a dependency in the property so Ember knows to re-evaluate when the data changes. Also, you don't need model.* in an object controller, the properties are automatically delegated to content/model
So:
formattedStart: function(){
console.log(this.get('startDate');)
return this.get('startDate');
}.property('startDate'),

EmberJS Sort / Filter an ArrayController decorated by an ObjectController

I'm struggling with limiting a data set represented by an ArrayController that also relies upon an ObjectController as a decorator for actions and computed properties. When I define a computed 'results' property and have it return either 'content' or 'arrangedContent', it seems my ObjectController (itemController) is entirely bypassed, and no references to the 'shipment' model are included.
Route:
App.ShipmentsManifestsRoute = Ember.Route.extend({
model: function() {
return this.store.find('manifest');
}
})
Models:
App.Shipment = DS.Model.extend({
from: DS.attr("string"),
tracking: DS.attr("string"),
manifest: DS.hasMany("manifest", { async: true }),
received: DS.attr("number", {
defaultValue: function() {
return moment(Firebase.ServerValue.TIMESTAMP);
}
})
});
App.Manifest = DS.Model.extend({
upc: DS.attr("string"),
quantity: DS.attr("number", { defaultValue: 1 }),
condition: DS.attr("string", { defaultValue: 'New' }),
status: DS.attr("string", { defaultValue: 'Accept' }),
title: DS.attr("string"),
notes: DS.attr("string"),
shipment: DS.belongsTo("shipment", { async: true }),
});
Controllers:
App.ManifestController = Ember.ObjectController.extend({
actions: {
save: function() {
this.get('model').save();
}
},
receivedDate: function() {
return moment(this.get('shipment.received')).format('YYYY-MM-DD');
}.property('shipment.received')
})
App.ShipmentsManifestsController = Ember.ArrayController.extend({
itemController: 'manifest',
sortProperties: ['shipment.received'],
sortAscending: false,
results: function() {
return this.get('arrangedContent').slice(0, 10);
}.property('arrangedContent.[]')
})
Also worth noting is that my itemController actions essentially don't seem to exist when using 'results' to render my data set. I have some inline editing functionality baked in that calls the 'save' action on the itemController, and Ember is throwing an error that 'save' doesn't exist.
This all of course works as it should if I iterate over {{#each controller}} rather than {{#each results}}.
I guess ultimately the problem is that 'content' doesn't return all of the data elements / properties / computed properties that would otherwise be available on the controller.
Is there a best practice way around this limitation?
UPDATE:
The problem is definitely related to the missing itemController when referencing arrangedContent. When iterating over the ArrayController directly, my View is referencing App.ManifestController as the controller. However, when iterating over arrangedContent, my View is instead referencing App.ShipmentsManifestsController as the controller. Still unsure as to why that is.
UPDATE 2:
Based on this, it looks like my issue is a duplicate of Setting itemController on a filtered subset of an ArrayController's model
A work-around that involves additional handlebars parameters was offered, which I will try. But would still love any input on whether this is intended behaviour or a bug.

prepare two model the same time in ember route

I need 2 model working together to show products at website.
I have a ProductController so product model is set properly, as i need option to show the products also, i need prepare it somewhere(means resolve the promise before controller run),
i think the setupController is the right place so i set 'option' property there, like below:
var Product = DS.Model.extend({
name: DS.attr('string'),
price: DS.attr('number'),
img: DS.attr('array')
});
var Option = DS.Model.extend({
productId: DS.attr('number'),
drawType: DS.attr('string'),
background: DS.attr('string'),
positionX: DS.attr('number'),
positionY: DS.attr('number')
});
App.ProductRoute = Ember.Route.extend({
setupController: function(controller, model) {
controller.set('model', model);
// i want to prepare option well so controller can get real data
// instead of a promise
this.store.find('option', 0).then(function(data){
controller.set('option', data);
});
})
})
App.ProductController = Ember.ObjectController.extend({
init: function(){
this._super();
console.log('can i get option in init?:', this.get('option'));
}
})
but it not work as i expected, the output in productController init is undefined. could anyone help, where am i wrong? thanks.
An idea would be to use the afterModel hook to fetch your option object and set it as an attribute on the route. Then in setupController you can set that object on the controller as well.
App.ProductRoute = Ember.Route.extend({
option: null,
afterModel: function() {
var _this = this;
return this.store.find('option', 0).then(function(data) {
_this.set('option', data);
});
},
setupController: function(controller, model) {
controller.set('model', model);
controller.set('option', this.get('option'));
}
});
However, I don't think you will have access to the option object (or even the model) in the init() function of the controller, since setupController() is called after init().
Side note - you may want to replace the field productId: DS.attr('number') in Option with product: DS.belongsTo('product').

Ember Data sideloaded properties being dropped on a model

I'm working with
Ember RC3
Ember Data Revision 12
Handlebars RC3
I have Ember Data sideloading relationships on many of my models, so that I can template the sideloaded relationships like so:
// Models
App.Client = DS.Model.extend({
company: DS.attr('string'),
accountNumber: DS.attr('string'),
startDate: DS.attr('mysqlDate'),
// Relationships
campaigns: DS.hasMany('App.Campaign'),
users: DS.hasMany('App.User'),
phones: DS.hasMany('App.Phone'),
addresses: DS.hasMany('App.Address')
});
App.User = DS.Model.extend({
email: DS.attr('string'),
password: DS.attr('string'),
// Relationships
userType: DS.belongsTo('App.UserType'),
role: DS.belongsTo('App.Role'),
clients: DS.hasMany('App.Client'),
phones: DS.hasMany('App.Phone'),
addresses: DS.hasMany('App.Address')
});
<!-- template -->
<script type="text/x-handlebars" data-template-name="user/index">
<h2>{{email}}</h2>
<h5>Clients</h5>
<ul>
{{#each client in model.clients}}
<li>{{client.company}}</li>
{{/each}}
</ul>
</script>
This works wonderfully...except for every 1 in 10 reloads or so. Every once in a while the sideloaded relationship (in this case the hasMany relationship model.clients) DOES NOT render to the template while all other model properties (not relationships) DO render to the template. What's weird is that it only does this every once in a while.
I'm not quite sure yet how I can set up a js fiddle for this problem, so I wanted to ask:
Where in the call stack could I set a break point to see what properties will actually get rendered?
I'm using {{debugger}} in the template in question, I'm just not sure where the best place would be to inspect the application state in the call stack.
So, my problem was two-fold. First Problem: Here's my router map and routes:
App.Router.map(function() {
this.resource('users', function() {
this.route('create');
this.resource('user', { path: ':user_id' }, function() {
this.route('edit');
this.route('delete');
});
});
});
App.UsersRoute = Ember.Route.extend({
model: function() {
return App.User.find();
}
});
// Default for this route
App.UserRoute = Ember.Route.extend({
model: function(params) {
return App.User.find(params.user_id);
}
});
Therefore, when navigating to the route 'clients/3' the DS.JSONSerializer would do an extract() for the UserRoute and an extractMany() for the UsersRoute. However, interestingly enough, most of the time extractMany() (for getting a JSON return of all of the users) would occur before extract() for the single user and its sideloaded properties. When this happened the sideloaded properties would indeed render to the template. However, every once in a while extract() would come before extractMany() (it asynchronosly "beat" the extract many), the sideloaded properties would not render. I think this is because if the extract() occured first that model would then be reset when the extractMany() then occurred for all of the models, which when extracting many do not have sideloaded properties.
I fixed this first problem by doing the following:
App.Router.map(function() {
this.resource('users', function() {
this.route('create');
});
this.resource('user', { path: 'user/:user_id' }, function() {
this.route('edit');
this.route('delete');
});
});
This prevented both models from being extracted in the same route, but the following might have solved both problems.
Second Problem: When navigating away from client/3 to clients and then back to client/3 again, the model would get reset just like the first problem—-sideloaded properties would get dropped.
The way to fix this was to use the UserRoute's activate hook to reload the model.
App.UserRoute = Ember.Route.extend({
activate: function() {
this.modelFor('user').reload();
}
});
This will force the model to be reloaded with the sideloaded properties every time this route 'activates', which is needed of this particular app we're building anyway.
Hope this helps somebody!
You may want to have a custom property that observes your association and print its content in the console.
printRelationship: function() {
console.log(model.clients.get('length'), model.clients);
}.computed(model.clients.#each);