Deserialize with an async callback - ember.js

I'm overriding the deserialize method so I can load an object from the backend corresponding with the id. However, the way I get this object is asynchronous. Deserialize does not wait for my callback and returns automatically.
Example:
show: Em.Route.extend({
route: "/:id",
deserialize: function(router, post) {
var postController = router.get('postController ');
postController.findById(post.id, function(post) {
return post
});
}
The call to the backend is made but deserialize returns automatically. Is there a way to work with asynchronous call in deserialize?
Thank you

Luke Melia did an NYC Ember meetup lightning talk on this very thing, using Promises via jQuery Deferred objects. Basically, Ember detects if you return a Promise (by seeing if the object has a then method), and puts the route in a loading state (which you have to declare as a sibling of the state with the deserialize). On ajax.done, you can resolve the promise, which puts the router fully in the state with the data fully loaded.
With the new router, you're not really supposed to use the support for async transitions that is present in the vanilla statemanager, but you can use the automatic loading states to achieve the same kind of thing.

Here is what i have found out, this would work in older versions of ember:
In the enter function of the state/route you can try to load the data. The enter function receives as second argument at transition object which have 2 methods. One is 'async' which tells the transition that it cant continue until the other method 'resume' has bean called.
So in the example:
...
enter: function (r, t) {
t.async();
r.get('postController').loadResources(t.resume);
},
...

For newer versions of Ember you should use some kind of proxy for the data you load.
{
PostController: Ember.ObjectController.extend({
find: function (id) {
var ajax = Em.$.ajax(); //call the ajax thing
ajax.done(Em.$.proxy(function (data) {
this.set('content', data);
}, this));
return this;
}
})
...
Router: Em.Router.extend({
show: Em.Route.extend({
deserialize: function(router, post) {
var postController = router.get('postController');
postController.find(post.id);
return postController;
}
});
})
}

Related

Can't set controller's content when async action in EmberJS

I'm getting stuck when I want to update my controller's content after a specific action :
this.set('content', CL.myModel.findALL());
I'm not using ember-data or any of them.
CL.myModel.findALL returns an Ember promise that returns itself the result in it's callback.
Actually, I've done the same thing in my setupController in the router and It works well.
Why does it not work in the controller itself ?
Thank you for helping.
You should do:
controller = this;
CL.myModle.findALL().then(function(models) {
controller.set('content', models);
})
I believe the reason that it works without this in Route#setupController is because, behind the scenes, Ember does something similar to the above code with the result of your model hook (which is typically a promise).
Another option you could look at is DS.PromiseObject (it's a nice abstraction for promises that wrap a promise and expose it's fulfillment value as content. You'd have to extract it from an ember-data build though.
Here is an example of my model :
CL.myModle.reopenClass({
findAll : function(){
return CL.Utils.ajaxPromise(r)
.then(this.findByIdSuccess.bind(this), this.findByIdFailed.bind(this));
},
findByIdSuccess: function (data) {
var negotiation = CL.NegotiationModel.create(data);
this.setMembers(negotiation);
this.setParties(negotiation);
this.setVersions(negotiation);
return negotiation;
},
})
Here is my controller :
onRefresh : function(){
this.set('content',CL.myModle.findAll())
}
As you can see in the model part I call some functions that do some model modification and I get it back findByIdSuccess callback.
I can't just return the datas I retrieve in the "then" of the promise.
My question again if it's still not clear : Is there any way to get the promise's data without doing :
CL.myModle.findALL().then(function(models) {
controller.set('content', models);
})

Can I use a pre-computed value as url parameter in emberjs

I am just playing with angularjs and the ui-router module and I am not satisfied with it.
I am missing a function/a process that allows me to execute logic before a state is activated.
The logic is to calculate the first day of the week of the current logged in user. Thus before the weekly view of a calendar is activated I have to execute this date logic so the user gets an url like:
/dateplanner/week/'firstdayOfWeek'
Its not enough for me to show a /dateplanner/week url.
Yes. In overriding a route's beforeModel function you can call this.transitionTo() and provide a different route and model as parameters. This will automatically abort the current route transition.
For example:
App.Router.map(function() {
this.resource('dateplanner', function() {
this.resource('week', { path: "/week/:firstDay" });
});
});
App.WeekRoute = Ember.Route.extend({
beforeModel: function(transition, queryParams) {
return someFunctionToGetFirstDayReturningPromise().then(function(firstDay) {
return this.transitionTo('dateplanner.week', firstDay);
});
}
});
You can find another example in the guide here (one that doesn't use promises or asynchronous code):
http://emberjs.com/guides/routing/redirection/#toc_based-on-other-application-state
API References:
http://emberjs.com/api/classes/Ember.Route.html#method_beforeModel
http://emberjs.com/api/classes/Ember.Route.html#method_transitionTo

Model.find().then() fires before records are actually loaded

I would like to load an entire collection and then just peel off records to use as models one at a time, without doing a roundtrip to the server every time.
I've figured out how to use Ember.Deferred to return a promise, but I can't get the promise to resolve at the right time. The following code just outputs "Found 0" ever time:
App.PersonRoute = Ember.Route.extend({
model: function(params) {
var name = "Erik";
var promise = Ember.Deferred.create();
App.people = App.Person.find();
App.people.then(function() {
console.log('Found ' + App.people.get('length'));
var person = App.people.findProperty('name', name)
promise.resolve(person);
});
return promise;
}
});
If I wrap the body of the then() in a setTimeout, and make it wait a couple seconds, everything works great.
Is there another event I can somehow bind to? I tried App.people.on('isLoaded'), but isLoaded is always true.
Thanks!
Is there another event I can somehow bind to?
Indeed there is an event you can listen to and that is didLoad.
I tried App.people.on('isLoaded'), but isLoaded is always true.
As for isLoaded there was a lot of confusion about this, see here for example, the confusion comes from the fact that the isLoaded flag is set to true by design when the store has finished loading the RecordArray for the records, even when initially empty because no record was already available locally. Then when the request to the server comes back the RecordArray will be populated with the records received from the backend, and bindings will kick off and your templates are updated.
As stated in the guides:
A record that is both loaded and clean means that is has received information about its attributes and relationships from the server, and no changes have been made locally on the client.
Was is stated above is what makes didLoad fire.
For more model related events you can listen to have a look at the guides under model lifecycle
Now to your setup, you could rewrite your code to something like this:
App.PersonRoute = Ember.Route.extend({
model: function(params) {
var name = "Erik";
var promise = Ember.Deferred.create();
App.people = App.Person.find();
App.people.on('didLoad', function() {
console.log('Found ' + App.people.get('length'));
var person = App.people.findProperty('name', name)
promise.resolve(person);
});
return promise;
}
});
Hope it helps.
If the promise isn't resolving at the right time, I think it would be a (fairly major)bug in Ember/data. I would suggest filling a bug with Emberjs/data.
I suspect though that your use of the different promise may be causing this bug.
App.Person.find is already returning a promise. You should use that promise itself when returning from the model(). Also to resolve that promise you simply return a result.
The implementation style for Promises that you have used is typically needed when you are integrating some external async api that doesn't support Promises.
I would refactor your model() like so. That might fix your async timing issue.
App.PersonRoute = Ember.Route.extend({
model: function(params) {
var name = "Erik";
var promise = App.Person.find();
promise.then(function() {
console.log('Found ' + App.people.get('length'));
return App.people.findProperty('name', name)
});
return promise;
}
});
I've found the docs on the Q library to be very useful when figuring out best way to use promises. Ember uses RSVP which is a different library but the principles are similar.

How do I make my items in my ArrayController listen to an event from a third party API?

I want my items in my ArrayController to listen to
FB.Event.subscribe('edge.create', function(response){
Ember.Instrumentation.instrument("facebook.like", response);
})
I'm making use of the a seperate itemController.
Like2win.ContestsController = Ember.ArrayController.extend({
itemController: "contest",
});
Like2win.ContestController = Ember.ObjectController.extend({
init: function() {
this._super();
instance = this;
Ember.subscribe("facebook.like", {
before: function(name, timestamp, payload) {
instance.send('onLike', payload);
},
after: function(name, timestamp, payload) {
//
}
})
},
For some reason only the last item in my array ends up listening to the event. I'm just starting out with Emberjs so I expect the answer to be simple.
Ember.Instrumentation is a simple software instrumentation api. It's purpose is performance profiling, tracing, not application level event dispatching.
You can see this api in action by setting Ember.STRUCTURED_PROFILE to true. This will start logging the render times for all templates rendered to the DOM by ember.
The specific issue you are having deals with how the Ember runloop works. The after hooks are only fired once with the last context given. This is done to ensure that multiple property changes of the same property do not result in re-rendering the DOM that many times. So the last property change on the runloop wins and the DOM updates with that property value.
What you really need to do is just translate the FB.Event of type edge.create into an application event that your app understands, something like `facebookLike', similar to what you have done above.
I would do this in the enter hook of your ContestRoute. Further exiting from the ContestRoute should probably unsubscribe from this event. So you probably need an unsubscribe in the exit hook.
enter: function() {
var self = this;
FB.Event.subscribe('edge.create', function(response) {
self.get('controller').send('facebookLike', response);
});
}
exit: function() {
// unsubscribe from edge.create events here
}
Then you can handle this event in your ContestController like so,
facebookLike: function(response) {
}

Can `insertNewline` invoke a transitionTo?

Sample code for my question is here.
It's a simple Ember app that displays the SearchView containing a TextField by default.
When the user enters some text and hits Enter, I want to transition to another state (displayUserProfile) passing the value entered in the textbox.
At first, in the Textbox's insertNewline callback, I called the transitionTo method of the application's router, passing the value as part of the parameter object:
App.SearchTextFieldView = Em.TextField.extend({
insertNewline: function() {
App.router.transitionTo('displayUserProfile', {
username: this.get('value')
});
}
});
That works fine, but then I noticed that pangratz's answer on a question about infinite scrolling, uses a different approach. Instead he invokes a method on the view's controller, which in turn calls a method on the controller's target (which is the router).
This changes my code to:
App.SearchTextFieldView = Em.TextField.extend({
insertNewline: function() {
Em.tryInvoke(this.get('controller'), 'displayUserProfile', this.get('value').w());
}
});
App.SearchController = Em.Object.extend({
displayUserProfile: function(username) {
this.get('target').transitionTo('displayUserProfile', {
username: username
});
}
});
My question is: which approach is better?
Calling transitionTo directly from the view or delegating it to the view's controller?
I would recommend a different approach. insertNewLine should trigger an action that is handled by the router, which will then transition its state.
App.SearchTextFieldView = Em.TextField.extend({
insertNewline: function() {
this.get('controller.target').send('showUser', {username: this.get('value')});
}
});
App.Router = Ember.Router.extend({
...
foo: Ember.Router.extend({
showUser: function(router, evt) {
router.transitionTo('displayUserProfile', evt);
});
}
});
You should put the showUser handler at the top-most route where it is valid in your app.
This approach follows the general pattern of events in Ember apps that views handle DOM-level events and where appropriate, turn them into semantic actions that are handled by the router.
Personally I think the second approach is better.
The first thing is that it's a bad idea to access the router statically. Then for me, you have to keep the views logic-less, so delegating to controller seems a good choice.
In your case this is only a call to the router, but you can imagine processing some algorithms on the textfield value. If you do this proccessing in you view, this will lead to a view, mixing UI code, and logic code. View should handle only UI code.