Edit 2013-03-02
This appears to be resolved in RC1
In previous versions of Ember.js, controllers would keep state assigned to them, but this seems to be an issue in Pre4.
So if I were to have this controller
App.UsersController = Ember.ArrayController.extend({
content: ['mike', 'jen', 'sofia'],
_content_observer: (function(){
/* I'm called, but my author doesn't know why */
console.log('Content was altered! But why? And by whom?');
}).observes('content')
});
The content is overwritten for some unexplained reason. I don't want to use ember data, but it seems like I'm being forced that direction.
This JS Fiddle exemplifies the issue.
What's going on? How do I stop it or is this so engrained in embers opinionatedness that I need to just accept it and go with the flow?
Edit
Taking this a bit further, it appears that whatever is setup as the model will be set to the content value, even if you override the setupController hook.
e.g.:
UsersRoute = Ember.Route.extend({
model: function() {
/*I should never be called, but I am. How curious.*/
return ['This','Shouldnt','Be','Assigned'];
},
setupController: function() {
/* According to http://emberjs.com/guides/routing/specifying-a-routes-model/, I should prevent the model from being assigned to content, but I don't */
}
});
The UsersController.content will end up with the value ['This','Shouldnt','Be','Assigned']
See this updated fiddle
This isn't really an ember-data thing. The new router sets controller's content property automatically. Instead of setting content from within the controller dedinition, customize the model that will be used for your route by overriding the model hook. For example:
App.UsersRoute = Ember.Route.extend({
model: function() {
return ['mike', 'jen', 'sofia', 'greta']
}
}
I modified your jsfiddle here: http://jsfiddle.net/WGYmg/
You may use the setupController method to set the controller's contents as you like:
setupController: function(controller) {
controller.set('content', []);
}
See this fiddle
Edit
You can use the model method to return the original content:
model: function () {
var c = this.controllerFor('users');
return c.get('content');
}
This is a bit hackish, but still..:)
See updated fiddle
Related
i just wanna refresh model in route while get an action from controller and run doRefresh action in this route
this is my code
import Ember from 'ember';
export default Ember.Route.extend({
profileFormService: Ember.inject.service(),
profileFormAtributeService: Ember.inject.service(),
model(){
return Ember.RSVP.hash({
profileForms: this.get('profileFormService').find(),
profileFormsAttributes: this.get('profileFormAtributeService').findByProfileFormId("1"),
inputTypes: {data: ['CHECKBOX', 'NUMBER_FIELD', 'PHONE_NUMBER', 'TEXT_FIELD', 'EMAIL', 'TEXT_AREA', 'RADIO_BUTTON', 'DESA', 'KABUPATEN_KOTA', 'PROVINSI', 'KECAMATAN', 'MAP_POINT', 'MAP_POLYGON']}
});
},
setupController(controller, model) {
this.controllerFor('backend.setting-profile-form-attribute').set('profileForms', model.profileForms);
this.controllerFor('backend.setting-profile-form-attribute').set('profileFormsAttributes', model.profileFormsAttributes);
this.controllerFor('backend.setting-profile-form-attribute').set('inputTypes', model.inputTypes);
},
actions:{
doRefresh(param){
let context = this;
this.get('profileFormAtributeService').findByProfileFormId(param).then(function (response) {
context.set("profileFormsAttributes",response);
}), function (e) {
this.debug(e);
};
}
}
});
unfortunately this is does'nt affect the profileFormsAttributes model.
I've ben trying to debug the model with this
this.debug(this.get('controller.model.profileFormsAttributes'))
this.debug(this.get('model.profileFormsAttributes'));
but the console log said undefined
can you resolve this and explain what happen in this my route..
thank's for your concern
Your problem is that you cannot achieve the object returned from within route in action handler directly like this.get('profileFormsAttributes'); hence your setting does not work.
this.get('controller.model.profileFormsAttributes');
this.get('model.profileFormsAttributes');
Even above two statements does not work; because you cannot retrieve model or controller like this.
You have two options; either you need to save what you are going to return from model directly within model hook with this.set('model', model) or you can achieve it with this.controllerFor(this.get('routeName')).get('model')
I would recommend the second approach for your case. Please take a look at the following twiddle that I have prepared to illustrate the case for you.
Please take a look at index.js where foo attribute of object returned from model hook is set with
Ember.set(this.controllerFor(this.get('routeName')).get('model'), 'foo', 'foo updated');
I hope this helps.
I'm working on a webapp to teach myself Ember, and I've walked into one large issue:
The page halts while it is attempting to fetch json, and my IndexRoute and IndexController feel very bloated. Additionally, this.store.find('pokemon') uses the RESTAdapater, and can freeze the page from rendering anything (besides the loader) for up to 1.5 seconds.
App.IndexRoute = Ember.Route.extend({
model: function() {
var store = this.store;
return Ember.RSVP.hash({
pokeballs: App.Pokeball.all(),
pokemon: store.find('pokemon'),
status: App.Status.all(),
levels: App.Levels
});
}
});
Updated Question: As it is now, my IndexController is larger than I would like, and is acting as a mediator for the pokeballs and pokemon collections. I am thinking it would be a good idea to split up IndexController so that I have an IndexController, a PokemonListController, and a PokeballListController. The problems I have are:
How should I populate the content of the PokemonListController and PokeballListController if I am on '/', which maps to the IndexRoute?
Is this actually a good idea, am I treating controller's they way they are intended to be treated?
Webapp Demo: http://theirondeveloper.github.io/pokemon-catch-rate
Github: https://github.com/TheIronDeveloper/pokemon-catch-rate
On one hand you are not tied to a single controller in a route, there is generally only a single controller associated with a route, but you can always set more controllers if you need them to, remember they are decorators of your models.
App.IndexRoute = Ember.Route.extend({
model: function() {
return store.find('pokemon');
},
setupController: function(controller, model) {
var pokemonListController = this.controllerFor('pokemons');
var pokeballListController = this.controllerFor('pokeball');
controller.set('model', model); //this would be the index controller
pokemonListController.set('model', model.pokemon);
pokeballListController.set('model', model.pokeballs);
}
});
Also you can render your page if you need to, without waiting for the responses, Ember will handle updating your UI once the response is received. if your response is too slow, the user will see the page, and an empty list (in this case, empty list of pokemon), and then once the request is resolved, the list will fill up with it.
To do that, just return an empty array from your model hook, and update it async:
App.IndexRoute = Ember.Route.extend({
model: function() {
var pokemon = [];
var store = this.store;
store.find('pokemon').then(function(allPokemon) {
pokemon = allPokemon; //untested, you may need to push them instead
});
return Ember.RSVP.hash({
pokeballs: App.Pokeball.all(),
pokemon: pokemon,
status: App.Status.all(),
levels: App.Levels
});
}
});
Not seeing anything "bloated" about your IndexRoute or IndexController. It is true that a lot of Ember apps will have multiple routes and thus multiple controllers, but that happens when it makes sense to switch to other routes. If it doesn't make sense for your application - then what you have is great.
If you have multiple routes (and thus multiple controllers), the approach #Asgaroth suggested will work great for setting multiple controllers. Otherwise, if you only have a single route - there is really no need to have multiple controllers.
The fact that your data gets fetched and that takes some time is normal. Now, ideally this (data fetching) should only happen once and your data would then get cached and as you peruse around your other routes (which you currently do not have) your data would already be available to you without any extra penalty.
If you do need to have multiple controllers and are wondering how to communicate between them, you are probably looking for the needs API outlined here.
UPDATE
I took another look at the model hook and it is weird how 3 out of 4 things in there are not promises at all and don't look like they belong in there.
So, here is how you can clean that up.
App.IndexRoute = Ember.Route.extend({
model: function() {
return this.store.find('pokemon');
}
});
That's the only thing that belongs in there. The other properties might as well be properties on your controller, as in:
App.IndexController = Ember.Controller.extend({
levels: function(){
return App.Levels;
}.property(),
pokeballs: function(){
return App.Pokeball.all()
}.property(),
status: function(){
return App.Status.all();
}.property(),
Of course, you would then need to change references to those properties in your template and other code. So, for example, you would change from model.pokeballs to just pokeballs. You would also change from model.pokemon to just model
I submitted a pull request to show you the way I did this - see here
Not a full answer, but to reveal the magic between the route and controller ... here is how the model gets drop'd into the controller instance for you
App.IndexRoute = Ember.Route.extend({
model: function() {
return store.fin('pokemon');
},
setupController: function(controller, model) {
//the model that gets returned from the above method is added to the controller instance for you in this generated method on the route
controller.set('model', model); //also alias'd as content in older versions of ember
}
});
I have an Ember app that has a series of nested routes, each with dynamic segments:
E.g
/NestedRouteA/argA/NestedRouteB/argB
In NestedRouteB route's model hook, I use argA and argB to find a given resource.
The problem I having is the model hook will not get called when argB stays constant, but argA changes
E.g
If a user is /NestedRouteA/1/NestedRouteB/1, and then goes to NestedRouteA/2/NestedRouteB/1, the model hook I am expecting to get called does not.
Any ideas how I can force the model hook to get called?
I created a JS fiddle here:
http://jsfiddle.net/ssirowy/P2P9n/1/
App.ParentRoute = Ember.Route.extend({
model: function(params){
return params.parent_num;
}
});
App.ParentChildRoute = Ember.Route.extend({
model: function(params){
var parent = this.modelFor('parent');
var child = params.child_num;
console.log("Retrieving model for parent/child combo");
return new Ember.RSVP.Promise(function(resolve){
setTimeout(function(){
resolve(child);
}, 1000);
});
}
});
The current version of the fiddle uses the latest Ember, and does not display the problem I had before.
If, however, you use <= Ember1.5, the problem shows itself.
I upgraded in my project and the problem went away.
Obligatory version info:
"DEBUG: Ember : 1.6.0-beta.1+canary.ffa2c83c"
"DEBUG: Ember Data : 1.0.0-beta.7+canary.d55198c2"
"DEBUG: Handlebars : 1.3.0"
"DEBUG: jQuery : 2.1.0"
I have an ember data model set up like this
App.User = DS.model.extend({
username: DS.attr(),
sites: DS.hasMany('site', {async:true})
});
And a route set up like this:
App.SitesRoute = Ember.Route.extend({
setupController: function (controller, model) {
this.controllerFor('auth').get('model.sites').then(function(sites){
controller.set('model', sites);
});
});
});
The auth controller gives me the currently logged in user and I want the Sites route to only display sites that are relevant to the currently logged in user.
This however does not work and throws an error
"Error while loading route: App.SitesRoute<.setupController"
What is interesting is that this setupcontroller hook actually works if I throw a break point in there, no error occurs and the data loads fine in to the UI.
This leads me to believe that there is a problem with the promise loading going on here but I cannot workout why. I would have thought that when the 'model.sites' promise fulfills the controllers model gets set which in turn populates the content and my ui. But this does not seem to be the case.
Any ideas? What am I doing wrong here?
Edit 1
Here is a variation which has similar results. if I put a break point on the return statement in the model hook of the route, it works. Otherwise it does not
App.SitesRoute = Ember.Route.extend({
model: function () {
return this.controllerFor('auth').get('model.sites');
},
setupController: function (controller, model) {
controller.set('model', model);
}
});
Edit 2
Ok here is something that makes this code work. But seems to me is contrary to the docs which state this:
"In cases where data is available asynchronously, you can just return
a promise from the model hook, and Ember will wait until that promise
is resolved before rendering the template."
I had thought that the call to self.controllerFor('auth').get('sites') was a promise if the 'sites' relationship was marked 'asyc:true' am I mistaken in this?.
So I can do this and it works, which I guess it kind of simulating the break point:
App.SitesRoute = Ember.Route.extend({
model: function() {
var self = this;
return new Ember.RSVP.Promise(function(resolve) {
Ember.run.later(function() {
resolve(self.controllerFor('auth').get('sites'));
}, 3000);
});
},
setupController: function(controller,model) {
controller.set('content', model);
}
});
Edit 3
Ok, I have found something that works, which I will go with for now, but I am still unhappy with not understanding what is going on or why. There is something that I neglected to mention which is that I am using the ember-data-django-rest-adapter I cant tell if this is what is causing the issue despite several hours of debugging the internals of ember and the adapter.
Without further ado here is what ended up working:
App.SitesRoute = Ember.Route.extend({
setupController: function(controller,model) {
this.controllerFor('auth').get('content').then(function (user) {
user.get('sites').then(function (sites) {
controller.set('content', sites);
});
});
}
});
Any insights to why this works when the other approaches don't would be greatly appreciated.
What I would do to keep all controllers aware of the current user is to inject the currentUserController into all my controllers in the ember initializer. Then, you can get the currentUser from any point in your controller by accessing it like this.get('currentUser').]
In your case, I would try this and see if it works:
model: function() {
return this.controllerFor('auth').get('sites');
},
setupController: function(controller,model) {
model.then(function(response) {
controller.set('content', response);
}
}
I've found that when I have a model relationship that uses async: true I can expect that when I get that relationship the result is a promise, for example:
var stuff = model.get('relatedCollection');
var actualStuff;
if (typeof stuff.then === 'function') {
stuff.then(function (collection) {
actualStuff = collection;
});
} else {
actualStuff = stuff;
}
The above is pseudo code to illustrate that when you use aysc: true when you define your model reltionship that you most likely will see a promise when you get that related collection.
I've noticed that when the promise is resolved, maybe even when you have a breakpoint set, then when you get the related collection you may see the actual collection instead of the promise as a result.
So, when I do use aysc: true I typically use the branching code to first check if the result is thenable typeof thing.then === 'function' If so, I treat the assigned value as a promise; and use the thenable syntax to get the actual collection of models after the promise is resolved.
In ember's official guide, it provides two ways to set the controller's underlying object.
First is setting the model property:
App.SongsRoute = Ember.Route.extend({
setupController: function(controller, playlist) {
controller.set('model', playlist.get('songs'));
}
});
Second is setting the content property:
MyApp.listController = Ember.ArrayController.create();
$.get('people.json', function(data) {
MyApp.listController.set('content', data);
});
Are these two properties represent the same thing? Which way should i use?
It seems they are the same thing,
https://github.com/emberjs/ember.js/blob/v1.3.0/packages/ember-runtime/lib/controllers/controller.js#L44
Ember.ControllerMixin = Ember.Mixin.create(Ember.ActionHandler, {
....
model: Ember.computed.alias('content'),
....
The model property is an alias for content.
Also,
https://github.com/emberjs/ember.js/blob/v1.3.0/packages/ember-routing/lib/system/route.js#L849
which mentions that,
By default, the `setupController` hook sets the `content` property of
the controller to the `model`.
UPDATE
Deprecated since v1.7.0 and the code placed in a mixin.
https://github.com/emberjs/ember.js/blob/v2.12.0/packages/ember-runtime/lib/mixins/controller.js
Along with the related deprecation mixin.
https://github.com/emberjs/ember.js/blob/v2.12.0/packages/ember-runtime/lib/mixins/controller_content_model_alias_deprecation.js
In the documentation - http://emberjs.com/api/classes/Ember.Controller.html#property_model - it clearly states that when retrieving or modifying a controller's model, the model property should be used instead of the content property.