Ember: How to get computed properties from a nested model? - ember.js

First: I have no idea how to work with promises in Ember.js.
I want to call a property of my controller which depends on async model-data which is also nested.
Also, my model looks something like that:
+-------------+         +------------+
| Method      | hasMany |  Practice  |
|             +--------->            |
|             |         |            |
+-------------+         +------------+
                              |       
                              | hasMany
                        +-----v------+
                        | Alpha      |
                        |            |
                        |            |
                        +------------+
So I created something like this:
allAlphas: function() {
var self = this;
var returnValue = "nichts";
var promises = {
allAlphas: self.get('model.method').then(function(method) {
//get the practices
return method.get('practices');
}).then(function(practices) {
//get the alphaSField in EVERY practice
//the alphasField is the (hasmany 'alpha')member in practice
var alphasFields = practices.getEach('alphas');
return Ember.RSVP.all(alphasFields).then(function() {
return alphasFields;
});
}).then(function(alphasFields) {
// here: get all the alphas via promise or something
})
};
Ember.RSVP.hash(promises).then(function(results) {
// return all the alphas (of all pracitces in the method) in some way
});
}.property()
There are two Problems (like already metioned in the comments):
How to load nested hasMany async models like all alphas in all practices.
How to return the complete result as a property in the RSVP.hash-Method for use in templates or something
Can anybody help me?
Edit 06/20/2015
As #Kingpin2k suggested, Ive added a Gist for better understanding of my Problem:
https://gist.github.com/MarcManhart/e5c1d91e8fdfd876de37

just return an array, and populate the array after the fact.
allAlphas: function() {
var self = this,
returnValue = [];
this.get('model.method').then(function(method) {
//get the practices
return method.get('practices');
}).then(function(practices) {
//get the alphasField in EVERY practice
//the alphasField is the (hasmany 'alpha')member in practice
var alphas= practices.getEach('alphas');
Ember.RSVP.all(alphas).then(function(resolvedAlphas) {
resolvedAlphas.forEach(function(afs){
returnValue.pushObjects(afs.toArray());
});
});
});
return returnValue;
}.property()
Update
It looks like pushObjects doesn't like the ED Collection (or maybe it doesn't like the promises underneath, I didn't look into it that much). Also we should use the resolved values instead of the promises sent in (alphas vs resolvedAlphas in my code below).
Example: http://emberjs.jsbin.com/cinobetoyu/1/edit?js,output

Related

How to get the changed object of an array when using ember oberver

I am using following code to get the details of the object whose property is changed.
getChangedObject:function(){
console.log('changed name is' , can i get the object here );
}.observes('model.#each.rollNo')
Thanks in advance.
Inside observer you cant get the exact object which caused observer to trigger, but you can try the below approach, like iterating array of model and watch for changed attributes alone.
getChangedObject: function() {
let model = this.get('model');
model.forEach(function(item, index) {
var changedAttributes = item.changedAttributes();
item.eachAttribute(function(item, meta) {
var temp = changedAttributes[item];
if(temp){
console.log(' old value ',temp[0],' new value ',temp[1]);
}
});
})
}.observes('model.#each.rollNo')

[emberjs]what's point of adding 'on("init")' to observabes?

I'm learning ember-cli-101. It adds on('init') when using observer. I tried to delete it , and it makes no difference.
I tried to read the api document and still make no sense, please help me out...
autoSave: function() {
var article = this.get('article');
if (!article.get('isNew')) {
this.sendAction('save', article);
}
},
stateChanged: function() {
var article = this.get('article');
if (article.get('isDirty') && !article.get('isSaving')) {
Ember.run.once(this, this.autoSave);
}
}.on('init').observes('article.state')
autoSave:.....
stateChanged: function() {
var article = this.get('article');
if (article.get('isDirty') && !article.get('isSaving')) {
Ember.run.once(this, this.autoSave);
}
}.observes('article.state')
They really works the same, what's the difference...
If the property is changed before the observer is initialized, the observer won't fire. This is why sometimes it is good to run fire the observer function also on init.

Ember.js global routed/transitioned events

Is it possible to subscribe to all transition events in the application? Or alternatively some observable property containing the current route?
I'm integrating with a third-party UI component that needs to be synchronized to the current route.
The application controller has a currentRouteName property, as explained here. It's mostly for debugging, but I imagine that it's a fairly stable property that could be used in production.
EDIT: If you need to be alerted of all changes, use the hashchange event like Ember does internally. This will only work if you're using hash based routing though. If you're using Ember's history API based routing, you'll have to use that.
In your app_controller you can add this snippet which fires on every path/route change
currentPathDidChange: function currentPathDidChange() {
var path = this.get('currentPath')
}.observes('currentPath')
I solved this by hooking into Router.didTransition
Live example: http://jsbin.com/yuzedacu/5/edit (modified the example found here)
App.Router.reopen({
updateCurrentRoute: function(infos) {
var appController = this.container.lookup('controller:application');
if (!('currentRoute' in appController)) {
Ember.defineProperty(appController, 'currentRoute');
}
if (infos && infos.length > 0) {
// The last part of the route contains the route name
var route = infos[infos.length - 1].name;
// Collect the dynamic route parameters
var params = infos.reduce(function(a, b) {
// Parameter can be named anything
// assume there are 0 or 1 parameters
for (var name in b.params) {
if (b.params.hasOwnProperty(name)) {
// 1 parameter
return a.concat(b.params[name]);
}
}
// 0 parameters
return a;
}, []);
var path = [route].concat(params);
Ember.set(appController, 'currentRoute', path);
} else {
Ember.set(appController, 'currentRoute', []);
}
},
didTransition: function(infos) {
this.updateCurrentRoute(infos);
return this._super(infos);
}
});

How can I simulate blur when testing directives in angularjs?

The problem
I am trying to test some directives (code for both below). One of them is an "email" (called "epost" in the code(norwegian)) directive. The solution to this should work for all of them, so I am keeping it to this one for now.
Technologies: Angularjs, Jasmine, Requirejs, (grunt & karma running in Chrome)
The directive validates email addresses in two ways; on upshift and on blur. I can test the upshift without problems as you can see in the test below, but I can't figure out how to simulate a blur so the bind('blur') in the directive runs.
What I have done
I have tried to catch the compiled element like this:
elem = angular.element(html);
element = $compile(elem)($scope);
And then in the test i tried several permutations to trigger the blur with a console log just inside the bind function in the directive. None of the below works. It does not trigger.
elem.trigger('blur');
element.trigger('blur');
elem.triggerHandler('blur');
element.triggerHandler('blur');
element.blur();
elem.blur();
I based the injection and setup on this: To test a custom validation angularjs directive
The email directive in angularjs wrapped in requirejs
define(function() {
var Directive = function() {
return {
require: 'ngModel',
link: function(scope, elem, attrs, ctrl) {
var pattern = /^[A-Za-z0-9._%+-]+#[A-Za-z0-9.-]+\.[A-Za-z]{2,4}$/;
elem.bind('blur', function() {
scope.$apply(function () {
if (!elem.val() || pattern.test(elem.val())) {
ctrl.$setValidity('epost', true);
} else {
ctrl.$setValidity('epost', false);
}
});
});
ctrl.$parsers.unshift(function(viewValue) {
if (pattern.test(viewValue)) {
ctrl.$setValidity('epost', true);
return viewValue;
} else {
return undefined;
}
});
}
};
};
return Directive;
});
The test (using jasmine and requirejs)
define([
'Angular',
'AngularMocks',
], function () {
describe('Directives', function () {
var $scope;
var form;
beforeEach(module('common'));
beforeEach(function () {
var html = '<form name="form">';
html += '<input type="text" id="epost" name="epost" epost="" ng-model="model.epost"/>';
html += '</form>';
inject(function ($compile, $rootScope) {
$scope = $rootScope.$new();
$scope.model = {
epost: null
};
// Compile the element, run digest cycle
var elem = angular.element(html);
$compile(elem)($scope);
$scope.$digest();
form = $scope.form;
});
});
describe('(epost) Given an input field hooked up with the email directive', function () {
var validEmail = 'a#b.no';
var invalidEmail = 'asdf#asdf';
it('should bind data to model and be valid when email is valid on upshift', function () {
form.epost.$setViewValue(validEmail);
expect($scope.model.epost).toBe(validEmail);
expect(form.epost.$valid).toBe(true);
});
});
});
});
I have been able to figure out where I went wrong after some breakpoint debugging.
The "element" item I get out using the approach described in the top of the question is not actually the directive it self. It's an object which wraps the form and the directive.
Like this
{ 0: // The form
{ 0: // The directive (input element)
{
}
}
}
To actually simulate a blur on the directive it self, I did something like this
var directiveElement = $(element[0][0]);
directiveElement.blur();
After getting the element I wanted, and wrapping it in a jQuery object (may be optional), it worked like a charm. I then used the approach like in the test in the question with $setViewValue and checked the model value like this.
form.epost.$setViewValue('a#b.no');
directiveElement.blur();
expect($scope.model.epost).toBe('a#b.no');
expect($scope.form.epost.$valid).toBeTruthy();
Hope this could be of help to others trying to figure the directive testing out.
I too ran into a similar problem and it mystified me. My solution was to use JQuery to get the input and then use angular.element(input).triggerHandler('blur') to make it work. This is odd to me because I do not have to do this with the click event.
spyOn(controller, 'setRevenueIsInvalid');
var sugarRow = $(element).find('tr#ve_id_5')[0];
var amount = $(sugarRow).find('input.amount')[0];
angular.element(amount).triggerHandler('blur');
expect(controller.setRevenueIsInvalid).toHaveBeenCalled();

Automatic type casting of vanilla objects to Ember objects

I'm just diving in to Ember. I'm looking for a way to pass a plain array of vanilla objects into a collection/controller and have them type cast to the correct model.
Here's the simple collection view:
{{#collection id="prods" contentBinding="Vix.prodsController" tagName="ul"}}
{{content.title}}
{{/collection}}
Here's the model:
Vix.Prod = Ember.Object.extend({
id: null,
title: null
});
And the controller:
Vix.prodsController = Ember.ArrayController.create({
content: []
});
Then let's get some JSON-formatted data from the server. In this example I'll just hard-code it:
var prods = [{id:"yermom1", title:"yermom 1"}, {id:"yermom2", title:"yermom 2"}]
Vix.prodsController.set('content', prods);
So far so good. I get my simple list of li elements displaying the titles as I'd expect. But when I want to update the title of one of the objects, using:
Vix.prodsController.objectAt(0).set('title', 'new title')
It complains because the object has no set method-- it has not been properly cast to my Vix.Prod Ember Object.
Using this alternative:
Vix.prodsController.pushObjects(prods);
Produces the same result. It's only if I explicitly create new model instances that I get the get/set goodness:
var prods = [Vix.Prod.create({id:"yermom1", title:"yermom 1"}), {Vix.Prod.create(id:"yermom2", title:"yermom 2"})]
Is there a way to automatically type cast those vanilla objects to my Vix.Prod Ember Object? If not, am I the only one that really wants something like that? In Backbone one can set the model property on a collection. I suppose I can create a setter on my Controller to do something similar- just wondering if there is something built-in that I'm missing. Thanks!
No magic. I'd suggest do a loop wrapping the model.
var prods = [{id:"yermom1", title:"yermom 1"}, {id:"yermom2", title:"yermom 2"}];
for (var i = 0; i < prods.length; i++) {
prods[i] = Vix.Prod.create(prods[i]);
}
If I use ember as much as I hope to, I'm going to want a shortcut. So here's what I've done for now. I created a base Collection class that I use to create my Collections/Controllers:
Vix.Collection = Ember.ArrayController.extend({
model: null,
pushObject: function(obj) {
if (this.get('model') && obj.__proto__.constructor !== this.get('model')) {
obj = this.get('model').create(obj);
}
return this._super(obj);
},
pushObjects: function(objs) {
if (this.get('model')) {
objs = this._typecastArray(objs)
}
return this._super(objs);
},
set: function(prop, val) {
if (prop === 'content' && this.get('model')) {
val = this._typecastArray(val);
}
return this._super(prop, val);
},
_typecastArray: function(objs) {
var typecasted = [];
objs.forEach(function(obj){
if (obj.__proto__.constructor !== this.get('model')) {
obj = this.get('model').create(obj);
}
typecasted.push(obj);
}, this);
return typecasted;
}
})
Now when I call pushObject, pushObjects, or .set('collection', data), if the collection instance has a defined model property and the objects being added to the collection aren't already of that type, they'll be cast. Been working good so far, but I welcome any feedback.
You should have a look at ember-data: https://github.com/emberjs/data
It seems to fit your needs...
As of today, it's not yet production ready (as stated in the readme), but is quickly converging toward maturity, thanks to an active development.