Here what i have done :
I have controller called "employee" and component "Department".
From controller i want to call the function of the component how can i it ?
controller: "Employee" ::
if (this.get("callMoveleft")) {
this.set("callMoveleft", false);
}
else {
this.set("callMoveleft", true);
}
Component : "Department" ::
callMoveLeft: function () {
console.log('Move left will be called');
}.observes("callMoveleft"),
Department's function is not getting called. Please suggest me where i am doing wrong ?
You should have a model that represents your component's state. This model will be available to the controller, so that it can call .moveLeft() on it.
The component should use this model's properties to display itself. Once the controller changes the model's state, the component will automatically update.
UPD
Demo: http://emberjs.jsbin.com/wizepi/1/edit?html,js,output
Related
I have a component in which I am observing a property from model and model is fed to controller in controller setup as controller property 'model'. Model has properties age, salary, rank. The property in component is to be entered by user.
The component will be called as:
{{ui-slider prop="age"}}
OR
{{ui-slider prop="salary"}}
This is not the complete component but it explains my problem.. The Component is :
App.UiSliderComponent = Ember.Component.extend({
prop:function(){
return this.get('prop');
}.property('prop'),
modelPropertyObserver:function(){
console.log("property is "+this.get('targetObject.model').get(this.get('prop'));
}.observes('targetObject.model.'+this.get('prop')),
didInsertElement:function(){
console.log("Element inserted");
}
})
This is not working. When I observe property like .observes('targetObject.model.age') then it works fine. But now it is showing cori_component.js:29 Uncaught TypeError: this.get is not a function
I also tried .observes('targetObject.model.'+this.prop) Though it doesn't show any error but the observer doesn't work.
How to concatenate the 'prop' property to the observer?
Or is there any way that I can concatenate the string inside component and then substitute it into observer.
Innstead you can try,
{{ui-slider prop=model.age}}
and
modelPropertyObserver: Ember.observer('prop', function() {
console.log("modelPropertyObserver");
})
I don't think the below one is acceptable.
.observes('targetObject.model.'+this.get('prop'))
You can try the following by overriding init and setting the property there (I've done this to have a dynamic computed property). I'm using Babel to have ES2015 features (like ...arguments and string interpolation
)
init() {
this._super(...arguments);
this.set('value', Ember.computed(`model.${this.get('attribute')}`, function() {
return this.get(`model.${this.get('attribute')}`);
}));
}
I assume by the documentation that you could replace Ember.computed with Ember.observer (and replace the corresponding method signature - snippet bellow copied from the documentation)
Ember.observer('value', function(sender, key, value, rev) {
// Executes whenever the "value" property changes
// See the addObserver method for more information about the callback arguments
}
Say I have the following component
{{my-component onSaved=(action 'save')}}
I want to define the "save" action on the route not the controller.
actions: {
save(model): {
return model.save();
}
}
The returned promise is important as I need it in the component code.
const pendingPromise = this.attrs.onSave(model);
This does not work and says it can't find the action. Is this possible somehow? Even if I provide an action in the controller to simply call the one in the route there is still no way to get hold of the returned promise.
actions: {
saveIntermediary(model) {
this.send('save', model); // can't get hold of the response!!!!
}
}
{{my-component onSave=(action 'saveIntermediary')}}
The route isn't connected to the template, that's why you can't normally bind a closure action from a route. There's two solutions at the moment:
Move the action to the controller. The controller is the context of the rendered template, so they're connected.
Use the route action addon and keep the action in the route.
I have a component which has, inside it, a list of child components (being drawn with a yield inside the parent component):
parent-component
for item in items
child-component item=item childProperty=parentProperty
Inside child-component, I have an observer on "childProperty", which correctly fires any time parentProperty changes. The problem is that I'd like to trigger that observer in a time when the property hasn't actually changed.
to do this, in my parent-component, I have:
this.notifyPropertyChange('parentProperty')
For some reason, this isn't making it to the child component. Here's a JS bin showing:
http://emberjs.jsbin.com/caxedatogazo/1/edit
While I'm happy to talk through my use-case more, I'm more interested in whether the JS bin should work, and if not, why..
Thanks so much for any help!
When you call notifyPropertyChange on the controller, only observers registered within the controller are notified of the property change.
In your case, the observer is within the component controller and not the parent controller from where the notifyPropertyChange is called.
There is a hacky way to ensure that the component controller is notified of the property change. This can be done by adding the following method to the Component.
didInsertElement: function() {
var controller = this.get('targetObject');
controller.addObserver('foo', this, this.onDataChange);
},
What we are doing is, getting the parent controller, registering an observer for foo with the parent controller.
Here is the emberjs fiddle for the same: http://emberjs.jsbin.com/rajojufibesa/1/edit
Hope this helps!
I expanded on ViRa's answer.
This code below will allow your components to be passed data with different property keys on the controller. For instance, if the controller has a property data or wants to use the model from the router, the property key does not matter. The component does not need to have a fixed property key that is always used on the controller, such as "foo", instead it will dynamically find it.
didInsertElement: function() {
var controller = this.get('targetObject');
// Find the key on the controller for the data passed to this component
// See http://stackoverflow.com/a/9907509/2578205
var propertyKey;
var data = this.get('data');
for ( var prop in controller ) {
if ( controller.hasOwnProperty( prop ) ) {
if ( controller[ prop ] === data ) {
propertyKey = prop;
break;
}
}
}
if (Ember.isEmpty(propertyKey)) {
console.log('Could not find propertyKey', data);
} else {
console.log('Found key!', propertyKey, data);
controller.addObserver(propertyKey, this, this.onDataChange);
}
}
Update: Here is a JSBin: http://emberjs.jsbin.com/nafapo/edit?console,output
I have a component that represent a map and after an action in my controller I want to call a method on the component to center the map. The code looks like this
App.PlacesController = Ember.Controller.extend({
actions : {
centerMap : function () {
// how to call from here to GoogleMapComponent.centerMap ??
}
}
});
App.GoogleMapComponent = Ember.Component.extend({
centerMap : function () {
}
});
template
{{google-map}}
<button {{action "centerMap"}}>Center Map</button>
I have found a workaround but I don't think this is the Ember way of doing this.
{{google-map viewName="mapView"}}
<button class="center-map">Center Map</button>
App.PlacesView = Ember.View.extend({
didInsertElement : function () {
this.$(".center-map").click(this.clickCenterMap.bind(this));
},
clickCenterMap : function () {
this.get("mapView").centerMap();
}
});
In Ember, views (Components are glorified views) know about their controller, but controllers do NOT know about views. This is by design (MVC) to keep things decoupled, and so you can have many views that are being "powered" by a single controller, and the controller is none the wiser. So when thinking about the relationship, changes can happen to a controller and a view will react to those changes. So, just to reiterate, you should never try to access a view/component from within a controller.
There are a few options I can think of when dealing with your example.
Make the button part of your component! Components are meant to handle user input, like button clicks, so you may want to consider making the button a part of the map component and handle clicks in the actions hash of your component. If this buttons is always going to accompany the map component, then I certainly recommend this approach.
You could have a boolean property on your controller like isCentered, and when the button is clicked it's set to true. In your component you can bind to that controller's property, and react whenever that property changes. It's a two-way binding so you can also change your locally bound property to false if the user moves the map, for example.
Controller:
...
isCentered: false,
actions: {
centerMap: {
this.set('isCentered', true);
}
}
...
Component:
...
isCenteredBinding: 'controller.isCentered',
onIsCenteredChange: function () {
//do your thing
}.observes('isCentered'),
...
Jeremy Green's solution can work if you mix in the Ember.Evented mixin into the controller (which adds the pub/sub trigger and on methods)
You can use on to have your component listen for an event from the controller, then you can use trigger in the controller to emit an event.
So in your component you might have something like this:
didInsertElement : function(){
this.get('controller').on('recenter', $.proxy(this.recenter, this));
},
recenter : function(){
this.get("mapView").centerMap()
}
And in your controller you could have :
actions : {
centerMap : function () {
this.trigger('recenter');
}
}
Bind a component property to the controller property in the template:
{{google-map componentProperty=controllerProperty}}
Then observe the component property in the component:
onChange: function () {
// Do your thing
}.observes('componentProperty')
Now every time controllerProperty is changed in the controller, onChange in the component will be called.
From this answer, second paragraph.
I think it's OK to have a reference in your controller to your component. It's true that your component encapsulates it's own behaviour, but public methods like reload etc. are perfectly fine.
My solution for this is to pass the current controller to the component and set a property on the controller within the component.
Example
template.hbs:
{{#component delegate=controller property="refComponent"}}
component.js:
init: function() {
this._super.apply(this, arguments);
if (this.get("delegate")) {
this.get('delegate').set(this.get("property") || "default", this);
}
}
Now in your controller you can simply get a reference to your component with this.get("refComponent").
Steffen
Inside of your component call:
var parentController = this.get('targetObject');
See: http://emberjs.com/api/classes/Ember.Component.html#property_targetObject
I'm facing a problem where I need to link state manager, controller and view together and at the same time avoid to get an ugly spaghetti code. And the question is : which of these object should be created first and has the responsibility to create the others ?
To be specific, here is my example. First the view is a subclass of a container view that has a collection view as child :
App.MyView = Ember.ContainerView.extend {
childViews: ['streamView']
streamView: Ember.CollectionView.extend {
}
}
The controller is just as subclass of Ember.ArrayController with a load method :
App.MyController = Ember.ArrayController.extend {
load: ->
console.log "loading data"
}
The state manager has a view state which will instantiate App.MyView :
App.MyStateManager = Ember.StateManager.extend {
initialeState: 'ready'
ready: Ember.ViewState.extend {
view: App.MyView
}
}
Now I need to run the following test :
controller = App.MyController.create {}
manager = App.MyStateManager.create {}
expect(manager.getPath('currentState.name').toEqual('ready')
expect(controller.load).toHaveBeenCalled()
streamView = manager.getPath('currentState.view.streamView.content')
expect(streamView.content).toEqual(controller.content)
In order to make the last expectation to work, I need to bind the content of my streamView that is a child of App.MyView with the controller content. How can I do this cleanly ?
Furthermore, how to pass a reference to the state manager to the view and the controller in order to notify the manager when some event occur so it need to transit to another state. For example a click on an item or controller did finish a job ?
Take a look at this gist by Yehuda Katz discussing the new router implementation.
https://gist.github.com/2728699
The suggestion seems to be to make the router and by extension the state manager the point at which the 3 layers are tied together.