temporary suspension of ember observers - ember.js

With Ember.js, I have the following scenario:
My Class has a property that require some manipulation at instanciation.
To achieve that, I'm calling a function that does the work from init. I also want to observe this propery change, so that if consumer will set new value at run time I will run my manipulation logic over the new value.
The problem is, that as part of the init flow, I'm setting myself the new value to the property after manipulation, and this invokes the trigger (as expected). I do not want this code to run twice.
Consider the following code. 'here' will be printed twice to the console.
var MyObj = Ember.Object.extend({
prop: null,
init: function init() {
this._super.apply(this, arguments);
this._applyProp();
},
_applyProp: function prop() {
console.log('here');
var prop = this.get('prop');
if (prop === 'Dan') {
prop = 'Hi' + prop;
}
this.set('prop', prop);
}.observes('prop')
});
MyObj.create({prop: 'Dan'});
Any advice will be really appreciated.

Having an observer that sets the property it is observing seems like a bad idea. Your particular example could be achieved using a computed property getter/setter:
var MyObj = Ember.Object.extend({
prop: function(key, value) {
if (value !== undefined) {
if (value == 'Dan') {
return 'Hi ' + value;
}
return value;
}
}.property(),
});
MyObj.create({prop: 'Dan'});
Would this be sufficient to cover your use cases?

Related

Promise result in Ember Data computed property

I'm trying to make a call to an external API and use the results as a computed property in my Ember Data model. The result is fetched fine, but the computed property returns before the Promise resolves, resulting in undefined. Is this a use case for an Observer?
export default DS.Model.extend({
lat: DS.attr(),
lng: DS.attr(),
address: Ember.computed('lat', 'lng', function() {
var url = `http://foo.com/json?param=${this.get('lat')},${this.get('lng')}`;
var addr;
var request = new Ember.RSVP.Promise(function(resolve, reject) {
Ember.$.ajax(url, {
success: function(response) {
resolve(response);
},
error: function(reason) {
reject(reason);
}
});
});
request.then(function(response) {
addr = response.results[0].formatted_address;
}, function(error) {
console.log(error);
})
return addr;
})
});
Use DS.PromiseObject. I use the following technique all the time:
import DS from 'ember-data';
export default DS.Model.extend({
...
address: Ember.computed('lat', 'lng', function() {
var request = new Ember.RSVP.Promise(function(resolve, reject) {
...
});
return DS.PromiseObject.create({ promise: request });
}),
});
Use the resolved value in your templates as {{address.content}}, which will automatically update when the proxied Promise resolves.
If you want to do more here I'd recommend checking out what other people in the community are doing: https://emberobserver.com/?query=promise
It's not too hard to build a simple Component that accepts a DS.PromiseObject and show a loading spinner while the Promise is still pending, then shows the actual value (or yields to a block) once the Promise resolves.
I have an Ember.Service in the app I work on that's composed almost entirely of Computed Properties that return Promises wrapped in DS.PromiseObjects. It works surprisingly seamlessly.
I've used the self.set('computed_property', value); technique in a large Ember application for about three months and I can tell you it have a very big problem: the computed property will only work once.
When you set the computed property value, the function that generated the result is lost, therefore when your related model properties change the computed property will not refresh.
Using promises inside computed properties in Ember is a hassle, the best technique I found is:
prop: Ember.computed('related', {
// `get` receives `key` as a parameter but I never use it.
get() {
var self = this;
// We don't want to return old values.
this.set('prop', undefined);
promise.then(function (value) {
// This will raise the `set` method.
self.set('prop', value);
});
// We're returning `prop_data`, not just `prop`.
return this.get('prop_data');
},
set(key, value) {
this.set('prop_data', value);
return value;
}
}),
Pros:
It work on templates, so you can do {{object.prop}} in a template and it will resolve properly.
It does update when the related properties change.
Cons:
When you do in Javascript object.get('prop'); and the promise is resolving, it will return you inmediately undefined, however if you're observing the computed property, the observer will fire again when the promise resolves and the final value is set.
Maybe you're wondering why I didn't returned the promise in the get; if you do that and use it in a template, it will render an object string representation ([object Object] or something like that).
I want to work in a proper computed property implementation that works well in templates, return a promise in Javascript and gets updated automatically, probably using something like DS.PromiseObject or Ember.PromiseProxyMixin, but unfortunately I didn't find time for it.
If the big con is not a problem for your use case use the "get/set" technique, if not try to implement a better method, but seriously do not just use self.set('prop', value);, it will give your a lot of problems in the long-term, it's not worth it.
PS.: The real, final solution for this problem, however, is: never use promises in computed properties if you can avoid it.
PS.: By the way, this technique isn't really mine but of my ex co-worker #reset-reboot.
Create a component (address-display.js):
import Ember from 'ember';
export default Ember.Component.extend({
init() {
var url = `http://foo.com/json?param=${this.get('lat')},${this.get('lng')}`;
Ember.$.ajax(url, {
success: function(response) {
this.set('value', response.results[0].formatted_address);
},
error: function(reason) {
console.log(reason);
}
});
}
});
Template (components/address-display.hbs):
{{value}}
Then use the component in your template:
{{address-display lat=model.lat lng=model.lng}}
The below works by resolving inside the property and setting the result.
Explained here:
http://discuss.emberjs.com/t/promises-and-computed-properties/3333/10
export default DS.Model.extend({
lat: DS.attr(),
lng: DS.attr(),
address: Ember.computed('lat', 'lng', function() {
var url = `http://foo.com/json?param=${this.get('lat')},${this.get('lng')}`;
var self = this;
var request = new Ember.RSVP.Promise(function(resolve, reject) {
Ember.$.ajax(url, {
success: function(response) {
resolve(response);
},
error: function(reason) {
reject(reason);
}
});
}).then(function(response) {
self.set('address', response.results[0].formatted_address);
})
})
});

TodoMVC with ember, id does not increment

I am following the getting started guide from emberjs, and am at the point where I can add todos. My problem is though that when I add a todo it has an id value of null - is there a practical way to auto increment this?
var TodosController = Ember.ArrayController.extend({
actions: {
createTodo: function() {
var title = this.get('newTitle');
if (!title.trim()) {
return;
}
var todo = this.store.createRecord('todo', {
title: title,
isCompleted: false
});
this.set('newTitle', '');
todo.save();
}
}
});
When you call this.store.createRecord() you have an "option" to have an id autogenerated (see here) Ultimately though, that responsibility is delegated to an adapter. If your adapter has generateIdForRecord() method - this will be used to create an id. So, for example, FixtureAdapter implements this method as follows (see here):
generateIdForRecord: function(store) {
return "fixture-" + counter++;
}
ember-data uses RestAdapter by default (see here), so you would need to add the method for the id to be generated on the client...

Do something when Ember component is instantiated?

I call a component like this:
{{Gd-text-input label="Specify" name="Specify" key="entry.810220554" triggerKey="tada" hideIf="Client"}}
I would like to run some javascript-code that sets an additional property to this component.
What I'm trying to run is something like this
//Convert string ot array.
GdRadioInput = Em.Component.extend({
init: function(){
var cs = this.get('contentString');
console.log('doesthiswork?');
if(cs){
this.set('content', eval(cs));
}
}
});
But it doesn't run. If someone could just provide a sample that console.logs a the value of a property of a component whenever that component is created, that would be very helpful.
You can run this code in the init method
init:function(){
this._super();
hideIf = this.get('hideIf');
key = this.get('key')
if(hideIf === key){
this.set('class', 'hide');
}
}
Good luck
PD: now this method is private: http://emberjs.com/api/classes/Ember.Component.html#method_init
I know this is an old question, but I wanted to update it with the new way to do things. Instead of overriding the init function, you can now cause a function to run on initialization with .on('init'). Example:
GdRadioInput = Em.Component.extend({
setupFunc: function(){
var cs = this.get('contentString');
console.log('doesthiswork?');
if(cs){
this.set('content', eval(cs));
}
}.on('init')
});
A follow-up: Just in case you are depending on a fully loaded DOM tree, you can use .on('didInsertElement') instead of on('init'):
GdRadioInput = Em.Component.extend({
setupFunc: function(){
var cs = this.get('contentString');
console.log('doesthiswork?');
if(cs){
this.set('content', eval(cs));
}
}.on('didInsertElement')
});
That event is fired when the view's element has been inserted into the DOM: http://emberjs.com/api/classes/Ember.Component.html#event_didInsertElement

Emberjs Computed Property Calculated for Unknown Reason

So I have what I think is a simple Ember Object.
App.Playlist = Ember.Model.extend({
_clips: [],
clips: function() {
var self = this;
if(this.get('clipIds')) {
this.get('clipIds').forEach(function(id) {
self.get('_clips').addObject({});
}
}
}.property('clipIds')
});
The problem is that the clips computed property gets called infinitely until it raises an exception Uncaught RangeError: Maximum call stack size exceeded
Ray, this should be defined differently. Computed properties are defined like functions and Ember will handle calling your function when it observes a change to whatever dependencies you define.
App.Playlist = Ember.Model.extend({
myComputed: function () {
return this.get('clipIds').map(function (id) {
return Ember.Clip.create({id: id});
});
}.property('clipIds.#each'),
});
this code would watch some property called "clipIds" (whatever that is) and would return a list of Ember.Clip objects based on that array of clipIds.
So here is how I ended up fixing this for now. Still not sure why the computed property gets called repeatedly.
App.Playlist = Ember.Model.extend({
clips: [],
loadClips: function() {
var self = this;
if(this.get('clipIds')) {
this.get('clipIds').forEach(function(id) {
self.get('clips').addObject({});
}
}
}.observes('clipIds.#each')
});

Observe non-ember globals

I want a computed property to observe a non-ember global: a specific key in localStorage. Is this possible? The following does not seem to cut it:
someProperty:function(){
//some functionality
}.property('localStorage.someKey')
Is it possible to do what I'm trying to do directly?
In general, you can observe regular JavaScript objects just fine. You just need to use Ember.get and Ember.set to modify them:
var pojo = {};
var MyObject = Ember.Object.extend({
bigEyeballs: function() {
var O_O = this.get('pojo.O_O');
if (O_O) { return O_O.toUpperCase(); }
}.property('pojo.O_O')
});
var obj = MyObject.create({ pojo: pojo });
console.log(obj.get('bigEyeballs'));
Ember.set(pojo, 'O_O', "wat");
console.log(obj.get('bigEyeballs'));
You can see this working in this JSBin.
Local Storage is a bit of a different matter, as it's not really a normal JavaScript object. You can create a small Ember wrapper around local storage, and use that for observation:
var LocalStorage = Ember.Object.extend({
unknownProperty: function(key) {
return localStorage[key];
},
setUnknownProperty: function(key, value) {
localStorage[key] = value;
this.notifyPropertyChange(key);
return value;
}
});
var storage = new LocalStorage();
var MyObject = Ember.Object.extend({
bigEyeballs: function() {
var O_O = this.get('pojo.O_O');
if (O_O) { return O_O.toUpperCase(); }
}.property('pojo.O_O')
});
var obj = MyObject.create({ pojo: storage });
console.log(obj.get('bigEyeballs'));
Ember.set(storage, 'O_O', "wat");
console.log(obj.get('bigEyeballs'));
You can see this live on JSBin.
In both cases, the important thing is that you will have to use Ember-aware setting and getting in order to observe these properties.