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').
Related
Even though i have set the RESPAdapter to take care of everything, it doesn't reach out to my server to get the data. My code is:
var App = Ember.Application.create({
LOG_TRANSITIONS: true
});
App.ApplicationAdapter = DS.RESTAdapter.extend({
host: '../api/'
});
App.Me = DS.Model.extend({
email: DS.attr('string'),
firstname: DS.attr('string'),
lastname: DS.attr('string')
});
App.WelcomemessageController = Ember.Controller.extend({
firstname:"I get rendered as i should",
model: function() {
return this.store.find('me');
}
});
And yeah, the property "firstname" gets rendered out just fine. And when inspecting with Chrome Devtools, no requests are being made.
In your case you just want to use a computed property, and not the model function. you could call it model, but it'd be slightly confusing since generally a controller decorates a model, and in this case it'd just be a property called model on the controller (in order to decorate a model, the controller needs to be an ObjectController or ArrayController)
App.WelcomemessageController = Ember.Controller.extend({
firstname:"I get rendered as i should",
user: function() {
return this.store.find('me');
}.property()
});
So i have the following models:
var User = DS.Model.extend({
name: DS.attr('string'),
defaultRealm: DS.belongsTo('realm', { async: true })
});
var Realm = DS.Model.extend({
name: DS.attr('string')
});
My router and route look like:
App.Router.map(function() {
this.route('index', { path: '/:user_id' });
this.route('realm', { path: '/realms/:realm_id' });
});
App.UserRoute = Em.Route.extend(
model: function (params) {
return this.store.find('user', params.user_id);
},
setupController: function (controller, model) {
controller.set('model', model);
}
);
And in my index template i have the following:
{{#link-to 'realm' model.defaultRealm}}
Realm
{{/link-to}}
Now everything works as expected except that the link-to generates a url like /realms/<App.RealmModel:ember728:541ddd0f29909d0000b6d407> instead of /realms/541ddd0f29909d0000b6d407. It looks like instead of serializing the id of the model it's serializing the entire DS.Model object (defaultRealm is actually a PromiseObject due to the relationship being async). What's going on here?
Current workaround:
Use an observer on the controller and use realmId in the template. Is this really necessary?
App.IndexController = Em.ObjectController.extend({
realmId: null,
modelObserver: Em.observer('model', function () {
var self = this;
self.get('model.defaultRealm').then(function (realm) {
self.set('realmId', realm.get('id'));
});
});
});
Versions:
ember#1.7.0
ember-data#1.0.0-beta.10
handlebars#1.3.0
When building the url it uses the serialize hook to find the realm_id on the route. You can easily work around this by using the id, there is likely something else wrong with it, but I'll have to play with it later.
{{#link-to 'realms' defaultRealm.id}}
Realm
{{/link-to}}
Example with async: http://emberjs.jsbin.com/OxIDiVU/1107/edit
In my EmberJS application I am displaying a list of Appointments. In an action in the AppointmentController I need to get the appointments owner, but the owner always returns "undefined".
My files:
models/appointment.js
import DS from 'ember-data';
export default DS.Model.extend({
appointmentStatus: DS.attr('number'),
owner: DS.hasMany('person'),
date: DS.attr('Date')
});
models/person.js
import DS from 'ember-data';
export default DS.Model.extend({
name: DS.attr('string')
});
templates/appointmentlist.js
{{#each appointment in controller}}
<div>
{{appointment.date}} <button type="button" {{action 'doIt'}}>Do something!</button>
</div>
{{/each }}
controllers/appointmentlist.js
export default Ember.ArrayController.extend({
itemController: 'appointment'
});
controllers/appointment.js
export default Ember.ObjectController.extend({
actions:{
doIt: function(){
var appointment = this.get('model');
var owner = appointment.get('owner'); //returns undefined
//Do something with owner
}
}
});
Now, I know I can change the owner-property to owner: DS.hasMany('person', {async: true}), and then handle the promise returned from appointment.get('owner');, but that is not what I want.
I have discovered that if I do this {{appointment.owner}} or this {{appointment.owner.name}} in the appointmentlist template, the owner record is fetched from the server. So I guess Ember does not load relationships unless they are used in the template.
I think that the solution to my problem is to use the appointmentlists route to fetch the record in the belongsTo relationship. But I can't figure out how.
Maybe something like this?
routes/appointmentlist.js
export default Ember.Route.extend({
model: function() {
return this.store.find('appointment');
},
afterModel: function(appointments){
//what to do
}
});
EDIT
I did this:
routes/appointmentlist.js
export default Ember.Route.extend({
model: function() {
return this.store.find('appointment');
},
afterModel: function(appointments){
$.each(appointments.content, function(i, appointment){
var owner= appointment.get('owner')
});
}
});
and it works, but I do not like the solution...
You are still asynchronously loading those records, so if you are fast enough you could still get undefined. It'd be better to return a promise from the afterModel hook, or just modify the model hook to do it all.
model: function() {
return this.store.find('appointment').then(function(appointments){
return Ember.RSVP.all(appointments.getEach('owner')).then(function(){
return appointments;
});
});
}
or
model: function() {
return this.store.find('appointment');
},
afterModel: function(model, transition){
return Ember.RSVP.all(model.getEach('owner'));
}
Another way to go is:
export default Ember.Controller.extend({
modelChanged: function(){
this.set('loadingRelations',true);
Ember.RSVP.all(this.get('model').getEach('owner')).then(()=>{
this.set('loadingRelations',false);
});
}.observes('model')
});
This way the transition finishes faster and the relations are loaded afterwards. The loading-state can be observed through loadingRelations.
When there are a lot of relations to load I think this gives a better UX.
You want to load all the assocations in the route, because you want to use Fastboot for search engines and better first time site opened experience.
Holding your assocation loading after primary models are loaded, might not be the best decision.
I am using a syntax to load all assocations in the route:
let store = this.store;
let pagePromise = store.findRecord('page', params.page_id);
let pageItemsPromise = pagePromise.then(function(page) {
return page.get('pageItems');
});
return this.hashPromises({
page: pagePromise,
pageItems: pageItemsPromise
});
And for this.hashPromises I got a mixin:
import Ember from 'ember';
export default Ember.Mixin.create({
hashPromises: function(hash) {
let keys = Object.keys(hash);
return Ember.RSVP.hashSettled(hash).then(function(vals) {
let returnedHash = {};
keys.forEach(function(key) {
returnedHash[key] = vals[key].value;
});
return returnedHash;
});
}
});
I've noticed that if i use the same controller for different routes it does not get reset so i can keep data shared between routes which is really helpful for me.
But i wonder... when does the controller reloads in ember? (runs the init and cleans all of his properties)?
And can i manually tell the controller to reload itself?
Thanks for the help guys :)
The controllers are generally singleton instances (excluding itemController instances), they live the life of the page.
If you need to reset some properties you can do it during setupController of the route in need.
App.FooRoute = Ember.Route.extend({
model: function(){
//return something...
},
setupController: function(controller, model){
this._super(controller, model);
controller.setProperties({foo:'asdf', bar: 'ewaf'});
}
});
or you can define some method on the controller that resets it all, and call it during the setupController. Computed properties are all marked dirty and recalculated automatically when the model behind the controller is swapped out.
App.FooRoute = Ember.Route.extend({
model: function(){
//return something...
},
setupController: function(controller, model){
this._super(controller, model);
controller.reset();
}
});
App.FooController = Ember.ObjectController.extend({
foo: 'asdf',
bar: 'wert',
reset: function(){
this.setProperties({foo:'asdf', bar: 'ewaf'});
}// if you want it to happen on init tack on .on('init') right here
});
on init
App.FooController = Ember.ObjectController.extend({
foo: 'asdf',
bar: 'wert',
reset: function(){
this.setProperties({foo:'asdf', bar: 'ewaf'});
}.on('init')
});
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.