Emberjs: cross-controller binding failing because 'content' doesn't exist yet - ember.js

I'm trying to use the 'needs' feature to allow one controller to obtain a value from another. Here's a JSFiddle that shows a stripped-down version of my app before binding a value: http://jsfiddle.net/kevinwh/WRxnE/4/
App.ApplicationController = Ember.ObjectController.extend({
init: function() {
this._super();
},
dishClicked: function() {
console.log('clicked');
this.incrementProperty('clickCount');
}
});
App.DishController = Ember.ObjectController.extend({
needs: ['application'],
init: function() {
this._super();
},
//clickCountBinding: 'controllers.application.clickCount'
});
Basically, my ApplicationController has a clickCount property that is updated (by an action) whenever one of the Dish links is clicked. Clicking on a link also activates the DishRoute via linkTo.
Now I'd like the contained DishController to also have access to ApplicationController's clickCount. So I add the 'needs' property and a clickCountBinding property (which will have to be uncommented in the JSFiddle). Then, when I click on a link I get a complaint:
assertion failed: Cannot delegate set('clickCount', 0) to the 'content' property of object proxy : its 'content' is undefined.
Apparently the binding is being activated before the model content is set on the controller. Since the controller is being set up by the linkTo, my DishRoute.model() and DishRoute.setupController() methods are not invoked. Also, the DishController.init() method isn't even called before the binding error happens.
I considered the possibility that I should just stick a content member object into the class (commented out in the JSFiddle), but doing that gives a bizarre result: the click count is incremented separately for the different links. Interesting, but not what I'm after.
So - how do I share the clickCount value across these controllers? Is there some other way to set up the content in the DishController so that the binding will work?

You've just slightly misunderstood the error message.
The issue is that you've subclassed the ApplicationController from ObjectController even though it doesn't have an underlying content object to proxy to, you should just user Ember.Controller in this case.
That being said, if you have a counter you should probably default it to zero anyway.
App.ApplicationController = Ember.Controller.extend({
clickCount: 0,
dishClicked: function() {
console.log('clicked');
this.incrementProperty('clickCount');
}
});
App.DishController = Ember.ObjectController.extend({
needs: ['application'],
clickCountBinding: 'controllers.application.clickCount'
});

Related

Ember.js 1.13: The right way to observe a passed-in attribute of component?

I'm trying to upgrade my project to Ember 1.13, and I'm a bit confused about the behavior of new attrs of component, especially when I have to observe them.
For example, my test component observes bar which is a parameter passed from outside. I know in Ember's new Glimmer engine, the component rerenders once any of its attribute changes. What I can't figure out is that the observer will also be fired at that time if I observe attrs.bar (I didn't modify bar!). But if I observe bar itself then it'll be fine.
The sample code:
HTMLBar:
{{test-cpnt foo=foo bar=bar}}
<input type="button" {{action 'tap'}} value="tap"/>
Controller:
foo: 1,
bar: 100,
actions: {
tap: function(){
this.set('foo', this.get('foo')+1);
}
}
Component:
App.TestCpntComponent = Ember.Component.extend({
barObv1: Ember.observer('bar', function(){
console.log('bar observer is fired!');
}),
barObv2: Ember.observer('attrs.bar', function(){
console.log('attrs.bar observer is fired!');
}),
});
By tapping the button to change foo's value, we will trigger barObv2 as well.
I've created a jsbin for demo:
https://jsbin.com/qiwivu/2/edit?js,console,output
Does anyone know why the observer got triggered?
Well, you don't have to use observers since you are in Ember 1.13, you can make use of didUpdateAttrs which is triggered whenever an attribute is updated.
Ember.Component.extend({
....
didUpdateAttrs({oldAttrs, newAttrs}) {
let oldBar = get(oldAttrs, 'bar.value');
let newBar = get(newAttrs, 'bar.value');
if (oldBar !== new Bar) {
// your logic here
}
}
....
});
If you intend to use observer, you can observe for the changes like below
barDidChange: Ember.observer('bar', function(){
console.log('bar observer is fired!');
});
Listening for attrs.bar will not work properly, whenever an attribute is updated (including init), attrs are mutated every time which causes the observers to trigger, for more refer here. attrs are supposed to be used with angle-bracket components. Here is a nice article why we should not use attrs for curly components.

Ember component cannot use access controller property via "needs"

I'm trying to change a controller's property from a component as follows(JSBIN example http://jsbin.com/gevuhu):
App.CategoryManagerController = Ember.Controller.extend({
selectedCategory: null,
});
App.BlogPostComponent = Ember.Component.extend({
needs: ['categoryManager'],
selectedCategory: Ember.computed.alias('controllers.categoryManager.selectedCategory'),
actions:{
selectedCategory: function (){
this.set('selectedCategory',1);
}
}
});
but getting the error Property set failed: object in path "controllers.categoryManager" could not be found or was destroyed.
Is it that we cannot use "needs" in components ?
Ember Components are completely isolated from surrounding context including controllers (see here). That's the bad news. The good news is that if you pass selectedCategory into the component, it will become 2-way bound, so any change to it in the component will be known by your controller.
So, your controller could be something like:
App.ApplicationController = Ember.ObjectController.extend({
needs: ['categoryManager'],
selectedCategory: Ember.computed.alias('controllers.categoryManager.selectedCategory'),
selectedCategoryChanged: function(){
alert("NEW CATEGORY: " + this.get('selectedCategory'));
}.observes('selectedCategory')
});
and then in your application template, you can say
{{ blog-post selectedCategory=selectedCategory }}
See a working example here
In later version like 2.2. We'll be writing this as:
App.ApplicationController = Ember.Controller.extend({
categoryManager: Ember.inject.controller("categoryManager")
});
and now, categoryManager will now have the controller named categoryManager.

Observes other childcontroller?

I am trying to observe another childcontroller.
I have the following router:
this.resource('generics', {path: '/gens'}, function() {
this.resource('generic', {path: '/:generic_id'}, function() {
this.route('prepare'); // Objectcontroller
this.route('sent'); // Objectcontroller
});
});
I have an observer in the sent controller, however it does not work. I have currently: 'controllers.sent.id' to get the id prepared in the prepare controller.
If I do a needs property with generic.prepare. It shows this error:
#needs must not specify dependencies with periods in their names (generic.sent)
I have also tried to use setupController to add the id to the sent controller properties, however the observer is worthless then.
it would be genericPrepare, but prepare/sent should never exist at the same time. You'd probably be better off sending the object to generic and then having sent grab the property off of it. Why are you against setting it up during setupController? The route will always hit setupController of sent before it's visible to the end user.

propertyBinding Controller - view

So, I am trying to get a simple propertyBinding to work with emberjs. Specifically, I have a controller with a content property, that gets updated under certain circumstances and a view, which needs that content array to draw some chart.
I have made the most basic example and it doesn't seem to work. My simple example is the following:
Appname.IndexController = Ember.Controller.extend({
value: 'bla'
});
Appname.IndexView = Ember.View.extend({
templateName: 'Index',
propertyBinding: 'Appname.IndexController.value',
didInsertElement: function() {
console.log('Indexview');
console.log(this.get('property'));
}
});
It is as simple as that, and it just does not work. What is really odd though, if I create another testcontroller (rather then extending it) e.g.
Appname.TestController = Ember.Controller.create({
value: 'jpopo'
});
the property binding works all of the sudden. But I just can not get it to work with the IndexController
(And in case the information is necessary, in the Applicaton.hbs I have an outlet)
Thanks for any help
Bindings work for instantiated objects, not for object definitions.
Appname.IndexController is the controller definition, not an instance. It is not what you want to bind to. The Ember.js app will create an instance of IndexController, and it's that created instance that you want to bind to:
To access the actual controller instance from its view, use controller.
Appname.IndexView = Ember.View.extend({
templateName: 'index',
propertyBinding: 'controller.value',
didInsertElement: function() {
console.log(this.get('property'));
}
});
Of course, that is if you follow Ember.js conventions.

How can I bind a property in my controller to the application controller if ApplicationController is automatically initialized by Ember?

I have a property in my Ember controller which I wanted to bind to a property in application controller but since I have not created an instance of ApplicationController, thus I am not able to give the reference. Something like,
MyApp.ApplicationController = Em.Controller.extend({
userName: 'hohenhiem'
});
MyApp.SampleController = Em.Controller.extend({
nameBinding: 'application.userName'
});
i have a jsFiddle to show my problem here
You are looking for the needs property. In a controller you can specify dependencies and then use them as follows:
MyApp.SampleController = Em.Controller.extend({
needs: ['application'],
nameBinding: 'controllers.application.userName' });
http://jsfiddle.net/yCr4F/1/