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
}
Related
I have a controller where I get the value from the hbs, which sends me the selected country value. I need this selected country in the model to compute and return back some results back to the hbs. How set this value in controller and get it in the model so I can compute using that value?
Well, there may be some different approaches to achieve this. However, I will give you some example which will hopefully help you.
//Controller.js
notes: Ember.computed('model.notes.[]', 'model.notes.#each.date', function() {
return this.get('model.notes').sortBy('date').reverse(); //This is an example of Computed function which in this case it's sorting notes based on date.
}),
blink: null,
actions: {
taskChangeColor: function() {
this.set('blink', 'blinker'); // this is another example that set new data by action which can be retrive from model and set to property
}
}
or another thing that you can do is to use Computed function in Model itself like
// model.js which is using ember-data and moment
timeZone: DS.attr(), //for example one property coming from server
utcOffsetFormat: Ember.computed(function() {
let time = moment.tz(this.get('timeZone')).format('hh:mm a');
return time;
// using a computed function to instantiate another value based on existing model property which means you can simpley use this property instead of direct one.
})
Additionally, you still are eligible to use action in Route.js instead of controller an example would be :
//route.js
actions: {
changeSave: function(step) {
var something = {
contact: this.currentModel,
};
this.currentModel.set('step', something.contact);
this.currentModel.save().then(d => {
// set your alert or whatever for success promise
return d;
}).catch(e => {
console.log(error(e.message));
return e;
});
},
in above example you can see that I have set an action to save notes in model which easily can set() to the model with exact same property name and if you do this you will get the result back immediately in your view.
hope it can help you. I recommend to read Ember-Docs
I would say, for your requirement you don't need controller properties for selectedCountryValue. You can keep this value in model itself.
In route,
setupController(model,transition){
this._super(...arguments); //this will set model property in controller.
Ember.set(model,'selectedCountryValue','US'); //you can set default value
}
and inside controller, you create computed property with dependent on model.selectedCountryValue. and compute some results
result:Ember.Computed('model.selectedCountryValue',function(){
//compute something return the result
}
In template, you can use {{model.selectedCountryValue}} directly.
I have a component that is provided each record ('data') of a model, along with 'meta' information that defines the attribute of the record to use, and renders it to a table. Within the component I'm trying to bind the underlying record attribute to each UI element {{tdVal}}:
tdVal : function(){
return Ember.computed.alias('data.' + this.get('meta').get('field'));
}.property()
Unfortunately this just renders [object object] in the UI. For comparison the following renders all of the items correctly, but obviously does not bind:
tdVal : function(){
return this.get('data').get(this.get('details').get('field'));
}.property()
Am I going about this in completely the wrong way? Any help would be very much appreciated.
UPDATE
To add clarity, if I bind to a literal key instead of an attribute key derived from the meta information I still have exactly the same problem, so I don't think it's an issue with using a derived key:
tdVal : function(){
return Ember.computed.alias('data.partner_id');
}.property()
UPDATE
If I set the binding against the component as an attribute rather than a function assigned to the attribute, then it works. Problem is I can't do this as the key for the alias needs to be derived and not a literal:
export default Ember.Component.extend({
tdVal : Ember.computed.alias('data.partner_id')
})
I found the solution to this. I think the computed alias was failing when returned from a function due to timing issues. Instead I added it to init()
export default Ember.Component.extend({
tagName : '',
init: function(){
this._super();
this.set('tdVal', Ember.computed.alias('data.' + this.get('details').get('field')));
}
});
This has done the trick, everything renders as it should and updates to the UI are reflected in the model and vice versa.
The second way looks right. It doesn't bind because you didn't provide the properties to bind to:
tdVal : function(key, value){
var path = 'data.' + this.get('details.field');
if (value)
this.set(path, value);
return this.get(path);
}.property('data', 'details.field')
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 an EmailsController (ArrayController), which stores all the emails. I have an EmailController (ObjectController) that has a parameter that stores if the actual Email is selected or not. I am trying to implement a button in the emails template, that selects or deselects all the Emails. So somehow I need to notify the EmailController via an action of the EmailsController and change the EmailController's isChecked parameter.
I am trying to use the itemController, the needs, and the controllerBinding parameters, but nothing works.
Here are the controllers:
App.EmailsController = Ember.ArrayController.extend({
needs: ["Email"],
itemController: 'Email',
checkAll: true,
actions: {
checkAllEmails: function() {
this.toggleProperty("checkAll");
console.log(this.get("checkAll"));
}
}
});
App.EmailController = Ember.ObjectController.extend({
needs: ["Emails"],
controllerBinding: 'controllers.Emails',
isChecked: true,
checkAllChanged: function() {
//this should execute, but currently it does not
this.set("isChecked",this.get('controller.checkAll'));
}.property("controller")
});
Here is the corresponding jsFiddle: http://jsfiddle.net/JqZK2/4/
The goal would be to toggle the selection of the checkboxes via the Check All button.
Thanks!
Your mixing a few different mechanisms and your using a few wrong conventions. It's not always easy to find this stuff though, so don't fret.
Referencing Controllers
Even though controllers are created with an Uppercase format, the are stored in the lowercase format and your needs property should be:
needs: ['emails'],
You then access other controllers through the controllers property:
this.get('controllers.emails.checkAll')
Computed Properties
Computed properties can be used as a getter/setter for a variable and also as a way to alias other properties. For example, if you wanted the isChecked property on the Email controller to be directly linked to the value of the checkAll property of the Emails controller, you could do this:
isChecked: function() {
return this.get('controllers.emails.checkAll');
}.property('controllers.emails.checkAll')
Although computed properties can do much more, this basic form is really just a computed alias, and there is a utility function to make it easier:
isChecked: Ember.computed.alias('controllers.emails.checkAll')
Observables
An observable basically creates a method that will be called when the value it observes changes. A computed alias would cause all items to uncheck or check whenever you clicked on any one of them, since their isChecked property is linked directly to the checkAll property of the parent controller. Instead of your checkAllChanged method identifying as a property it should use observes:
checkAllChanged: function() {
this.set("isChecked",this.get('controllers.emails.checkAll'));
}.observes("controllers.emails.checkAll")
This way when the checkAll property changes on the parent controller, this method updates the isChecked properties of all items to its value, but if you uncheck or check an individual item, it doesn't affect the other items.
Bindings
Bindings are somewhat deprecated; from reading issues on the Ember github repository I believe the creators of Ember seem to favor using computed properties, aliases, and observables instead. That is not to say they don't work and if your goal was to avoid having to type out controllers.emails every time, you could create one like you did (I wouldn't call it controller though, cause thats really quite ambiguous):
emailsBinding: 'controllers.emails'
Using a computed alias instead:
emails: Ember.computed.alias('controllers.emails')
You could then change your observer to:
checkAllChanged: function() {
this.set("isChecked",this.get('emails.checkAll'));
}.observes("emails.checkAll")
Heres an updated version of your jsFiddle: http://jsfiddle.net/tMuQn/
You could just iterate through the emails, changing their properties from the parent controller. You don't need to specify needs or observe a variable.
App.EmailsController = Ember.ArrayController.extend({
itemController: 'email',
actions: {
checkAllEmails: function() {
this.forEach(function(email) {
email.toggleProperty("isChecked");
});
}
}
});
Also, you typically don't set initial values like you did with isChecked = true; I believe that's creating a static shared property on the prototype (not what you intended). Instead, set the property on init, or pass it in from your original json data.
See the code: http://jsfiddle.net/JqZK2/5/
I have a controller in Ember like so:
App.TasksController = Ember.ArrayController.extend({
search: function(term){ ... }
})
And I have the relative view, with a custom text field, as such:
App.TasksView = Ember.View.extend({
searchField: Ember.TextField.extend({
keyUp: function(){ this.get('controller').search() }
})
})
I however get an error saying that there is no such method.
I was wondering:
How can I correctly call the method defined in the controller from the view?
How can I debug which is the currently active controller? If I console.log(this.get('controller')) I get an object, but it is very confusing to navigate and to understand exactly which controller is that.
the scope of this on the text field isn't the same scope as the tasksview, so it doesn't have access to the controller.
Honestly a better way to handle this is to bind the textfield value to something and add an observer to it. When it changes fire off a search (or probably better would be to debounce it so you aren't firing off requests every single character they type)
http://emberjs.jsbin.com/IRAXinoP/3/edit