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

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?

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.

this.transitionToRoute not working in my controller Ember

I am using a controller to read the value selected on a drop down menu, take in parameters of some input fields and then save the record. It creates the record and takes in the information just fine. My problem lies when I try to transition to another page at the end of the action. I keep getting the error: Cannot read property 'transitionToRoute' of undefined
I am completely stumped. Any ideas?
Here is my controller code:
var teamId;
export default Ember.Controller.extend({
auth: Ember.inject.service(),
actions: {
onSelectEntityType: function(value) {
console.log(value);
teamId = value;
return value;
},
createProcess: function(processName, processDescription) {
var currentID = this.get('auth').getCurrentUser();
let team = this.get('store').peekRecord('team', teamId);
let user = this.get('store').peekRecord('user', currentID);
let process = this.get('store').createRecord('process', {
team: team,
user: user,
name: processName,
description: processDescription
});
process.save().then(function () {
this.transitionToRoute('teams', teamId);
});
}
}
});
Here is the corresponding route:
export default Ember.Route.extend({
auth: Ember.inject.service(),
model: function() {
var currentID = this.get('auth').getCurrentUser();
return this.store.find('user', currentID);
}
});
You should have clear understanding about this keyword in Javascript. The keyword this only depends on how the function was called, not how/when/where it was defined.
function foo() {
console.log(this);
}
// normal function call
foo(); // `this` will refer to `window`
// as object method
var obj = {bar: foo};
obj.bar(); // `this` will refer to `obj`
// as constructor function
new foo(); // `this` will refer to an object that inherits from `foo.prototype`
Have a look at the MDN documentation to learn more.
You can cache the this in normal variable this and then access inside the call back.
var self = this;
process.save().then(function () {
self.transitionToRoute('teams', teamId);
});
ECMASCript 6 introduced arrow functions whose this is lexically scoped. Here, this is looked up in scope just like a normal variable.
process.save().then(() => {
this.transitionToRoute('teams', teamId);
});

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.

Teach me how to design a nested computed property in 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(),