Intersection of promises and dependent computed properties - ember.js

I have a user model which has 2 relations (myFriends and friendsWithMe). The intersection is the Array of users which represents the real friends. I have solved this Computatation with RSVP.all :
friends: function() {
var ret = [];
Ember.RSVP.all([this.get('myFriends'), this.get('friendsWithMe')]).then(function(results) {
ret.pushObjects(_.intersection(results[0].get('content'), results[1].get('content'))) ;
});
return ret;
}.property('myFriends.#each', 'friendsWithMe.#each'),
The Problem is now I have another computed property that depends on this one:
/**
* Gives the relation between two User
* 4: has requested your friendship
* 3: Yourself
* 2: Friends
* 1: FriendShip Request
*/
myFriendshipStatus: function() {
if(this.get('friends').contains(this.container.lookup('user:current'))){
return 2;
} else if(this.get('friendsWithMe').contains(this.container.lookup('user:current'))){
return 4;
} else if(this.get('myFriends').contains(this.container.lookup('user:current'))){
return 1;
} else if (this.get('id') === this.container.lookup('user:current').get('id')){
return 3;
} else {
return 0;
}
}.property('friends.#each')
When I now debug myFriendShipStatus the promises are not resolved and the "friends" array has no entries yet.
I have also tried to change my friends function to the ember.computed.intersect, which would then look like this:
friends: function() {
return Ember.computed.intersect('myFriends', 'friendsWithMe')
}.property('myFriends.#each', 'friendsWithMe.#each'),
But then I get an exception from this line:
if(this.get('friends').contains(this.container.lookup('user:current'))){
Because the ArrayComputedProperty has no function contains.
How can get my friends function together with myFriendShipStatus working? I would prefer to use Ember.computed.intersect, but I don't know how I check then for it's values.

The reason it returns an empty array in the first example is as follows. Immediately after the Ember.RSVP.all() call, the return statement will be executed, returning an empty ret array. At some point in the future the RSVP promise will fulfill, but since the friends function has already returned the empty array, this will have no effect.
Here is what you could do:
// See http://emberjs.com/api/#method_A
friends: Ember.A,
recalculateFriends: function() {
Ember.RSVP.all([this.get('myFriends'), this.get('friendsWithMe')]).then(function(results) {
var myFriends = results[0], friendsWithMe = results[1];
this.set('friends', _.intersection(myFriends.get('content'), friendsWithMe.get('content')));
});
}.property('myFriends', 'friendsWithMe'), // #each is redundant here
myFriendshipStatus: function() {
// Will be recalculated when the friends array changes (which will in turn recalculate when myFriends or friendsWithMe changes
}.property('friends'),
And... I'm just now noticing you're using Ember.computed.intersect wrong :P It shouldn't be wrapped inside a function:
friends: Ember.computed.intersect('myFriends', 'friendsWithMe')
(See example: http://emberjs.com/api/#method_computed_intersect),

Related

Ember Calling method inside the controller

here is my code I have to call another method inside the Ember controller
I was tried but this is not working I have confused in this...
please help me to what is wrong in this code?
export default Ember.Controller.extend({
....
getValue(){
var a = 7 * 2;
return a;
},
getResult(){
var result = this.getValue(); // result is this.getValue is not function
}
});
First this code does not makes sense:
getValue(){
var a = a * 2;
return a;
},
here you're using a before you declare it.
but to answer your question: your code is correct.
Here is a working example. I've just replaced var a = a * 2; by var a = 7 * 2; to make this code valid.
My assumption is that whatever is calling getResult() is not setting the context of this to the controller, and my hunch is that you'll need to put getResults() in the actions object.

How can I delete records in a loop?

I'm writing a business service which needs to iterate all the records in a BC, and delete some of them:
bc.ExecuteQuery(ForwardBackward);
var isRecord = bc.FirstRecord();
while (isRecord) {
if (...) {
bc.DeleteRecord();
}
isRecord = bc.NextRecord();
}
However, DeleteRecord moves the cursor to the next one, so I would be skipping a record for each one being deleted, which is not what I want. Is there any standard-ish way to solve this?
I guess I could just make two loops, one for the checks and another for the deletion... it would work, but it just feels silly to iterate twice over the records, there has to be a better way than this:
bc.ExecuteQuery(ForwardBackward);
var isRecord = bc.FirstRecord();
var list = [];
while (isRecord) {
if (...) {
list.push(bc.GetFieldValue("Id"));
}
isRecord = bc.NextRecord();
}
bc.ClearToQuery();
bc.SetSearchSpec("Id", "='" + list.join("' OR ='") + "'"); // "='1-ABCD' OR ='1-1234'"
bc.ExecuteQuery(ForwardBackward);
while (bc.FirstRecord()) {
bc.DeleteRecord();
}
Solution will depend directly on your if (...) condition:
If it's a simple condition, which can be converted to a search
expression, then you just simply can make a query and delete
everything that was found.
If it's a complex condition, which cannot implemented as a searchspec, then you can do this in two ways (both has its own disadvantages):
a) Use awkward combination of DeleteRecord and PreviousRecord.
b) Create second method that will delete single record and call it for each record that satisfy the condition, something like that
function deleteRecord(sId:chars) {
...
bc.SetSearchSpec("Id", sId);
bc.ExecuteQuery(ForwardOnly);
if (bc.FirstRecord()) {
bc.DeleteRecord();
}
}
...
bc.ExecuteQuery(ForwardBackward);
var isRecord = bc.FirstRecord();
while (isRecord) {
if (...) {
deleteRecord(bc.GetFieldValue("Id"));
}
isRecord = bc.NextRecord();
}
...
Your solution can be and option c), but it will not work when Id list will be too big, because such query will lead to a huge SQL statement and databases have some limitations on SQL query size (and DBA won't be happy to see such queries in logs).
I prefer option b). You can optimize it by creating a second instance of the same BC in your main function and pass it by reference to the deleteRecord function.
Since you are doing ForwardBackward, you could just add a PreviousRecord call after deleting:
bc.ExecuteQuery(ForwardBackward);
var isRecord = bc.FirstRecord();
while (isRecord) {
if (...) {
bc.DeleteRecord();
bc.PreviousRecord();
}
isRecord = bc.NextRecord();
}
Or simply do the loop backwards:
bc.ExecuteQuery(ForwardBackward);
var isRecord = bc.LastRecord();
while (isRecord) {
if (...) {
bc.DeleteRecord();
}
isRecord = bc.PreviousRecord();
}
Are are storing the value of a first record in var and then in the loop you are checking if records are presents. I suggest you go for forward and backward try.
The value of var is incremented, So, Inside the while loop place the move back an element command and so var will now store the previous element. After executing the loop. You can easily increment to the next element.

Pass variables to ember handlebars

I have an Ember.js ArrayController and some handlebar code that looks like this
<p>{{length}} {{pluralize length "thing"}}</p>
Then I've got a handlebar helper that looks like
Handlebars.registerHelper('pluralize', function(count, str){
debugger;
return (count > 1 ? str+"s" : str);
}
);
When the debugger breaks I observe seeing that count = 'length' not a number like I would expect.
So what gives? What's the correct way to accomplish my obvious task.
Working fiddle here. http://jsfiddle.net/MwTuw/2/
The trick is to use Ember.registerBoundHelper which passes all the relevant data as a final argument to the function.
Ember.Handlebars.registerBoundHelper('pluralize', function (count) {
var options = Array.prototype.pop.call(arguments);
var string = options.data.properties[1];
return (count > 1 ? string+"s" : string);
});
This removes the {{if controller.length}} hack that is required with the other solution and means that adding or removing additional objects will update the value accordingly.
How about this:
Ember.Handlebars.registerHelper('pluralize', function (property, options) {
var count = Ember.Handlebars.get(this, property, options);
var _options = options;
count = parseInt(count, 10); //to be sure ...
if (count > 1) {
_options = _options.concat('s');
}
return new Handlebars.SafeString(_options);
});
EDIT
Here is a working fiddle
EDIT 2
Here is your working updated fiddle
Basically the problem was that the handlebar helper was acting when the controller still had no records, I've added a if helper to the template that listen on the controller.length and fires when it changes and so invoking also the handlebar helper to parse the value.
Hope it helps
Using Ember.registerBoundHelper will make all the key-value pairs in the template available as an hash on the 2nd parameter of the helper:
Handlebars template:
{{orderShow dataOrderBy key1="value1" key2="value2" ... keyN="valueN"}}
Javascript:
Ember.Handlebars.registerBoundHelper('orderShow', function(order, options) {
if(options) {
for(var prop in options.hash) {
alert(prop + '="' + options.hash[prop] + '"')
}
}
return order;
}
This behaviour is described at the end of the following page: http://handlebarsjs.com/expressions.html

How *not* to destroy View when exiting a route in Ember.js

Regarding the new Ember.js routing system (described here), if I understand correctly, views are destroyed when you exit a route.
Is there any way to bypass destruction of views upon exiting a route, so that the state of the view is preserved when the user re-enters the route?
Update: Looks like, views are not destroyed unless the outlet view is being replaced in the new route. For e.g., if you are in stateA with ViewA in some {{outlet master}} and you go to stateB with ViewB in {{outlet master}}, then ViewB will replace ViewA. A way around this is to define multiple outlets when you need to preserve views, e.g., {{outlet master1}}, {{outlet master2}}, ...
A nice feature would be the ability to pass an array of views to the outlet. And also be able to choose whether views will be destroyed or just become hidden, upon exiting a route.
I have since figure out how to modify the routing system, so that views inserted into outlets are not destroyed. First I override the Handlebars outlet helper, so that it loads an Ember.OutletView into {{outlet}}:
Ember.Handlebars.registerHelper('outlet', function(property, options) {
if (property && property.data && property.data.isRenderData) {
options = property;
property = 'view';
}
options.hash.currentViewBinding = "controller." + property;
return Ember.Handlebars.helpers.view.call(this, Ember.OutletView, options);
});
Where Ember.OutletView extends Ember.ContainerView as follows:
Ember.OutletView = Ember.ContainerView.extend({
childViews: [],
_currentViewWillChange: Ember.beforeObserver( function() {
var childViews = this.get('childViews');
// Instead of removing currentView, just hide all childViews
childViews.setEach('isVisible', false);
}, 'currentView'),
_currentViewDidChange: Ember.observer( function() {
var childViews = this.get('childViews'),
currentView = this.get('currentView');
if (currentView) {
// Check if currentView is already within childViews array
// TODO: test
var alreadyPresent = childViews.find( function(child) {
if (Ember.View.isEqual(currentView, child, [])) {
return true;
}
});
if (!!alreadyPresent) {
alreadyPresent.set('isVisible', true);
} else {
childViews.pushObject(currentView);
}
}
}, 'currentView')
});
Basically we override _currentViewWillChange() and just hide all childViews instead of removing the currentView. Then in _currentViewDidChange() we check if the currentView is already inside childViews and act accordingly. The Ember.View.isEqual is a modified version of Underscore isEqual:
Ember.View.reopenClass({
isEqual: function(a, b, stack) {
// Identical objects are equal. `0 === -0`, but they aren't identical.
// See the Harmony `egal` proposal: http://wiki.ecmascript.org/doku.php?id=harmony:egal.
if (a === b) return a !== 0 || 1 / a == 1 / b;
// A strict comparison is necessary because `null == undefined`.
if (a == null || b == null) return a === b;
// Unwrap any wrapped objects.
if (a._chain) a = a._wrapped;
if (b._chain) b = b._wrapped;
// Compare `[[Class]]` names.
var className = toString.call(a);
if (className != toString.call(b)) return false;
if (typeof a != 'object' || typeof b != 'object') return false;
// Assume equality for cyclic structures. The algorithm for detecting cyclic
// structures is adapted from ES 5.1 section 15.12.3, abstract operation `JO`.
var length = stack.length;
while (length--) {
// Linear search. Performance is inversely proportional to the number of
// unique nested structures.
if (stack[length] == a) return true;
}
// Add the first object to the stack of traversed objects.
stack.push(a);
var size = 0, result = true;
// Recursively compare objects and arrays.
if (className == '[object Array]') {
// Compare array lengths to determine if a deep comparison is necessary.
size = a.length;
result = size == b.length;
if (result) {
// Deep compare the contents, ignoring non-numeric properties.
while (size--) {
// Ensure commutative equality for sparse arrays.
if (!(result = size in a == size in b && this.isEqual(a[size], b[size], stack))) break;
}
}
} else {
// Objects with different constructors are not equivalent.
if (a.get('constructor').toString() != b.get('constructor').toString()) {
return false;
}
// Deep compare objects.
for (var key in a) {
if (a.hasOwnProperty(key)) {
// Count the expected number of properties.
size++;
// Deep compare each member.
if ( !(result = b.hasOwnProperty(key) )) break;
}
}
}
// Remove the first object from the stack of traversed objects.
stack.pop();
return result;
}
});
So that the state of the view is preserved when the user re-enters the
route.
I would, instead, store that information in the controller (or the state manager) so that when the route is re-entered, the new view is initialized with the old state. Does that make sense? So, for example, if it's a list of posts, and one is selected, you would store the data about which post was selected in the controller (or the state manager). After visiting a specific post and then coming back to the list, that same post would be selected.
I can imagine a use case where this wouldn't be very useful (e.g. scrolling to a specific position in a long list) so maybe that doesn't answer your question.

Reflection on EmberJS objects? How to find a list of property keys without knowing the keys in advance

Is there a way to retrieve the set-at-creations properties of an EmberJS object if you don't know all your keys in advance?
Via the inspector I see all the object properties which appear to be stored in the meta-object's values hash, but I can't seem to find any methods to get it back. For example object.getProperties() needs a key list, but I'm trying to create a generic object container that doesn't know what it will contain in advance, but is able to return information about itself.
I haven't used this in production code, so your mileage may vary, but reviewing the Ember source suggests two functions that might be useful to you, or at least worth reviewing the implementation:
Ember.keys: "Returns all of the keys defined on an object or hash. This is useful when inspecting objects for debugging. On browsers that support it, this uses the native Object.keys implementation." Object.keys documentation on MDN
Ember.inspect: "Convenience method to inspect an object. This method will attempt to convert the object into a useful string description." Source on Github
I believe the simple answer is: you don't find a list of props. At least I haven't been able to.
However I noticed that ember props appear to be prefixed __ember, which made me solve it like this:
for (f in App.model) {
if (App.model.hasOwnProperty(f) && f.indexOf('__ember') < 0) {
console.log(f);
}
};
And it seems to work. But I don't know whether it's 100% certain to not get any bad props.
EDIT: Adam's gist is provided from comments. https://gist.github.com/1817543
var getOwnProperties = function(model){
var props = {};
for(var prop in model){
if( model.hasOwnProperty(prop)
&& prop.indexOf('__ember') < 0
&& prop.indexOf('_super') < 0
&& Ember.typeOf(model.get(prop)) !== 'function'
){
props[prop] = model[prop];
}
}
return props;
}
Neither of these answers are reliable, unfortunately, because any keys paired with a null or undefined value will not be visible.
e.g.
MyClass = Ember.Object.extend({
name: null,
age: null,
weight: null,
height: null
});
test = MyClass.create({name: 'wmarbut'});
console.log( Ember.keys(test) );
Is only going to give you
["_super", "name"]
The solution that I came up with is:
/**
* Method to get keys out of an object into an array
* #param object obj_proto The dumb javascript object to extract keys from
* #return array an array of keys
*/
function key_array(obj_proto) {
keys = [];
for (var key in obj_proto) {
keys.push(key);
}
return keys;
}
/*
* Put the structure of the object that you want into a dumb JavaScript object
* instead of directly into an Ember.Object
*/
MyClassPrototype = {
name: null,
age: null,
weight: null,
height: null
}
/*
* Extend the Ember.Object using your dumb javascript object
*/
MyClass = Ember.Object.extend(MyClassPrototype);
/*
* Set a hidden field for the keys the object possesses
*/
MyClass.reopen({__keys: key_array(MyClassPrototype)});
Using this method, you can now access the __keys field and know which keys to iterate over. This does not, however, solve the problem of objects where the structure isn't known before hand.
I use this:
Ember.keys(Ember.meta(App.YOUR_MODEL.proto()).descs)
None of those answers worked with me. I already had a solution for Ember Data, I was just after one for Ember.Object. I found the following to work just fine. (Remove Ember.getProperties if you only want the keys, not a hash with key/value.
getPojoProperties = function (pojo) {
return Ember.getProperties(pojo, Object.keys(pojo));
},
getProxiedProperties = function (proxyObject) {
// Three levels, first the content, then the prototype, then the properties of the instance itself
var contentProperties = getPojoProperties(proxyObject.get('content')),
prototypeProperties = Ember.getProperties(proxyObject, Object.keys(proxyObject.constructor.prototype)),
objectProperties = getPojoProperties(proxyObject);
return Ember.merge(Ember.merge(contentProperties, prototypeProperties), objectProperties);
},
getEmberObjectProperties = function (emberObject) {
var prototypeProperties = Ember.getProperties(emberObject, Object.keys(emberObject.constructor.prototype)),
objectProperties = getPojoProperties(emberObject);
return Ember.merge(prototypeProperties, objectProperties);
},
getEmberDataProperties = function (emberDataObject) {
var attributes = Ember.get(emberDataObject.constructor, 'attributes'),
keys = Ember.get(attributes, 'keys.list');
return Ember.getProperties(emberDataObject, keys);
},
getProperties = function (object) {
if (object instanceof DS.Model) {
return getEmberDataProperties(object);
} else if (object instanceof Ember.ObjectProxy) {
return getProxiedProperties(object);
} else if (object instanceof Ember.Object) {
return getEmberObjectProperties(object);
} else {
return getPojoProperties(object);
}
};
In my case Ember.keys(someObject) worked, without doing someObject.toJSON().
I'm trying to do something similar, i.e. render a generic table of rows of model data to show columns for each attribute of a given model type, but let the model describe its own fields.
If you're using Ember Data, then this may help:
http://emberjs.com/api/data/classes/DS.Model.html#method_eachAttribute
You can iterate the attributes of the model type and get meta data associated with each attribute.
This worked for me (from an ArrayController):
fields: function() {
var doc = this.get('arrangedContent');
var fields = [];
var content = doc.content;
content.forEach(function(attr, value) {
var data = Ember.keys(attr._data);
data.forEach(function(v) {
if( typeof v === 'string' && $.inArray(v, fields) == -1) {
fields.push(v);
}
});
});
return fields;
}.property('arrangedContent')