Teach me how to design a nested computed property in ember.js - ember.js

I have a handful of computed properties defined on a component. I'd like to refactor these computed properties to live within a messages object on the component. When I make a call to get one of the computed properties elsewhere, I'm returned an instance of Ember's ComputedProperty object, rather then the translation string I expected. Looking at the documentation, Ember.get should invoke the computed property and return the object itself, the property value or null.
What am I missing? How would I go about structuring these nested computed properties so that I can access them using the get/set interface elsewhere in the component?
App.ValidatedDateComponent = Ember.Component.extend({
format: null,
label: null,
messages: {
invalidDateMsg: (function() {
return I18n.t('%{date} must be a valid date. %{format}', {
date: this.get('label'),
format: this.get('format')
});
}).property('label', 'format')
},
validate: function(value, status) {
if (!moment(value).isValid()) {
return status(false, Ember.get(this.messages, 'invalidDateMsg'));
} else {
return this._super(value, status);
}
}
});

Ember only supports defining computed properties while extending Ember.Object class, the exception to the rule is while defining a Ember.Mixin.
Defining the top level of the nest
var nest = Ember.Object.extend({
foo: function() {
return "something";
}.property()
});
Creating an instance of it
App.IndexController = Em.ArrayController.extend({
messages: nest.create()
});
Template
{{messages.foo}}
http://emberjs.jsbin.com/UhUvOvU/1/edit
So in your case you could, if you really wanted to, do:
messages: Em.Object.extend({
invalidDateMsg: function() {
return I18n.t('%{date} must be a valid date. %{format}', {
date: this.get('label'),
format: this.get('format')
});
}.property('label', 'format')
}).create(),

Related

template won't render from model object after sorting

I am sorting an array of objects queried from Ember-Data by 'type'-key before returning them in the model()-method of an Ember.Route to be rendered.
app/routes/test.js
export default Ember.Route.extend({
model() {
let obj = Ember.Object.create({a: [], b: [], c: []});
this.get('store').findAll('obj').then(function(unsorted){
unsorted.forEach(function(item) {// sort per type
obj.get(item.get('type')).addObject(item);
});
return obj;
});
}
});
The array returned by (the promise of) the Ember-Data query looks like this (all objects have Ember internal properties)
[
{
_id: '1',
type: 'a',
properties: {...}
},
{
...
},
{
_id: '15',
type: 'b',
properties: {...}
}
]
And the new object with objects sorted by "type"-key looks like this
{
a: [{
_id: '1',
type: 'a',
properties: {...}
},
...
],
b: [
... ,
{
_id: '15',
type: 'b',
properties: {...}
},
c: [...]
};
app/routes/test.hbs
<h2>Test</h2>
<h3>Type a</h3>
{{#each model.a as |source|}}
<div>
{{source.properties.prop1}}
</div>
{{/each}}
The template doesn't render the part that loops over the array, nor does the Ember-inspector plugin list the Model property under "Own properties" of the route (right panel in "View tree"-mode).
However, when returning a POJO (literally pasting object with array's by key in code) everything behaves as expected.
I suspect this has something to do with the internal (Ember-)properties of the objects returned by Ember-data (I have read about the owner of an object etc.) but I can't seem to figure this out ...
The answer of #Pavol gets to the basic problem, however return this.store...then(...) will work because this is how promises work. Read the documentation.
So this will work:
model() {
let obj = Ember.Object.create({a: [], b: [], c: []});
return this.get('store').findAll('obj').then(function(unsorted){
unsorted.forEach(function(item) {// sort per type
obj.get(item.get('type')).addObject(item);
});
return obj;
});
}
.then(onFulfilled) returns a new promise, that will resolve with the return value of the onFulfilled-callback. Creating a new promise with new Promise(...) is always totally useless if you already have promises and only for interoperability with callback-style code.
You are missing return statement in your model() hook:
export default Ember.Route.extend({
model() {
let obj = Ember.Object.create({a: [], b: [], c: []});
this.get('store').findAll('obj').then(function(unsorted){
unsorted.forEach(function(item) {// sort per type
obj.get(item.get('type')).addObject(item);
});
return obj;
});
// no return value before, findAll() returns promise, thus model() returns undefined
}
});
However, adding return this.store... won't solve your situation since you are updating different object after your promise is solved (the then() function).
I would recommend wrapping it in a promise and resolve it passing a modified object based on your sorting logic.
Note: I did not consider whether sorting at this stage makes sense and is of 'Ember-way'. Sorting usually happens via computed properties on controller/component level.
export default Ember.Route.extend({
model() {
return new Ember.RSVP.Promise((resolve)=>{
let obj = Ember.Object.create({a: [], b: [], c: []});
this.get('store').findAll('obj').then(function(unsorted){
unsorted.forEach(function(item) {// sort per type
obj.get(item.get('type')).addObject(item);
});
resolve(obj);
});
});
}
});
Update: Of course this.store() returns a valid promise (which I pointed out in the snippet but explained incorrectly later) and so you do not have to wrap it, sorry for the confusement! I haven´t realized that return value of the callback passed to then() is automatically internally passed in a promise, although I use promise chaining and this on a daily basis, too. Shame on me! Thx #Lux for putting this on a right way.

Compare two Ember objects (created with Ember.Object.create method)

I created a custom object with the Comparable mixin and added it to a model as an attribute with Ember transforms
var customObject = Ember.Object.extend(Ember.Comparable, {
compare: function() {
debugger;
}
});
Once the model is ready I create a copy of the custom object and add it as an attribute to the so that I can compare the custom object when it changes to this original value
export default DS.Model.extend({
custom: DS.attr("custom-object"),
ready: function() {
this.set("originalCustom", Ember.Object.create(this.get("custom")));
},
isUpdated: function() {
return Ember.compare(this.get("custom"), this.get("originalCustom"));
}
});
I manually call isUpdated to check if the compare method is invoked, but it never gets hit.
What am I missing here?

Ember custom view exception: had no action handler for: searchEvent

I have my custom view :
export default Ember.ContainerView.extend({
classNames: ['search-terms'],
eventTypeValue: null,
userSidValue: null,
init: function() {
this._super();
this.eventTypeValue = Ember.TextField.create();
this.userSidValue = Ember.TextField.create();
this.eventTypeValue.set("placeholder", "search by event type");
this.eventTypeValue.addObserver("value", this.userSidValue, this.change);
this.userSidValue.set("placeholder", "earch by user sid");
this.userSidValue.addObserver("value", this.userSidValue, this.change);
this.pushObject(this.eventTypeValue);
this.pushObject(this.userSidValue);
},
change: function() {
this.get("controller").send("searchEvent");
}
});
And controller :
export default Em.Controller.extend({
actions: {
searchEvent : function() {
console.log("controller searchEvent");
}
}
});
And when I change text in some fields, then I have following exception:
Uncaught Error: had no action handler for: searchEvent
But this working when I type some text and then click somewhere out of my custom view.
Your problem is the second argument to addObserver - this is the context that the third argument (this.change) is executed with.
Even though you specify this.change it doesn't use this - it uses the Ember.TextField as the this not the ContainerView.
You need to change the following two lines:
this.eventTypeValue.addObserver("value", this.userSidValue, this.change);
this.userSidValue.addObserver("value", this.userSidValue, this.change);
to:
this.eventTypeValue.addObserver("value", this, this.change);
this.userSidValue.addObserver("value", this, this.change);
This is a working JSBin example
I've commonly seen strings passed as the method (the third argument) - this also works. I would pass strings instead of the actual function itself.
this.eventTypeValue.addObserver("value", this, 'change');
this.userSidValue.addObserver("value", this, 'change');

How do I check if an ember computed property has a setter defined?

Say I have an Ember.Object obj, with a property propPath.
I'm trying to implement:
function isComputedPropertyWithNoSetter(obj, propPath) {
// what do I do here?
// something involving Ember.meta(obj) perhaps?
}
So I can do:
var hasStaticProp = Ember.Object.extend({ prop: 5 }).create();
isComputedPropertyWithNoSetter(hasStaticProp, 'prop');
// => false
var hasComputedPropertyWithSetter = Ember.Object.extend({ prop: function (k, v, d) { }.property() }).create();
isComputedPropertyWithNoSetter(hasComputedPropertyWithSetter, 'prop');
// => false
var hasComputedPropertyNoSetter = Ember.Object.extend({ prop: function () { }.property() }).create();
isComputedPropertyWithNoSetter(hasComputedPropertyNoSetter, 'prop');
// => true
I'm writing 'tree-walking' state serialization code for a large established ember codebase. When I restore state, I want a guard check to make sure I never accidentally overwrite a read-only (getter only) computed property with a static value.
I need to implement this function so I can do....
if (!isComputedPropertyWithNoSetter(obj, propPath) {
// not going to accidentally overwrite a computed property with a static value
Ember.set(obj, propPath, serializedStaticValue);
}
I realize this is fairly dicey, and the solution might be a not entirely recommended hack.

Returning a Promise from a Computed Property

I realize there have been several questions similar to this, but none of those answers seems to be resolving my issue. My objective is to take a list of language's, and filter them so that my template can display a subset of the full list.
I started off by verifying that my computed property is working:
MyController.js
// Works as expected
languagesFiltered: function() {
return this.get('languages');
}.property('languages')
Then I added in a filter function, but here's where I ran into trouble:
MyController.js
languagesFiltered: function() {
// console.log shows that languages is actually a promise
var languages = this.get('languages');
// all of this returns a promise, but Handlebars can't handle the promise
return languages.then( function( languagesArray ) {
return languagesArray.filter( function( item, index, enumerable) {
return item.get('name') !== 'English';
});
})
}.property('languages')
I'm attempting to use the Ember.Array.filter method (http://emberjs.com/api/classes/Ember.ArrayProxy.html#method_filter). The filter seems to be working correctly, but now languagesFiltered returns a promise, and Handlebars can't handle that.
I tried one last alternative:
MyController.js
languagesFiltered: function() {
var languages = this.get('languages');
// "return languages;" works
// But "return languages.filter" returns an empty array
return languages.filter( function( item, index, enumerable ) {
console.log(item);
return true;
});
}.property('languages')
And console.log(item) never gets called. So my questions are:
What is the best way to implement the simple filter I'm after?
This is a read-only computed property, but what are best practices for handling async values in computed properties?
I'm using Ember 1.7.0-beta4, Ember Data 1.0.0-beta10, and ember-cli 0.44. I'd upgrade to Ember 1.7.0, but there's a small bug that affects another part of our app, so we're waiting until 1.7.1. Thanks for your input!
You can try returning a PromiseArray instead of just the promise.
You should be able to do something like..
languagesFiltered: function() {
// all of this returns a promise, but Handlebars can't handle the promise
var promise = this.get('languages').then( function( languagesArray ) {
return languagesArray.filter( function( item, index, enumerable) {
return item.get('name') !== 'English';
});
})
return DS.PromiseArray.create({
promise: promise
});
}.property('languages')
Now there are few better solutions. I use ember-promise-helpers.
So can keep your languagesFiltered code intact and do the following inside your hbs:
{{#each (await languagesFiltered) as|lang|}}
...
...
More – https://emberigniter.com/guide-promises-computed-properties/