Ember.js - Asyncronous call in model find() method - javascript

I have implemented find() and findAll() methods on my Property model. Both methods make asynchronous calls to an API. findAll() is called while connecting the outlets for my home route, and works fine. find() is called by Ember.js while connecting the outlets for my property route. Note that find() is not called when navigating to a property route through actions, but is called when you go directly to the route through the URL.
Here is my router:
App.Router = Ember.Router.extend({
root: Ember.Route.extend({
showProperty: Ember.Route.transitionTo('property'),
home: Ember.Route.extend({
route: '/',
connectOutlets: function(router) {
router.get('applicationController').connectOutlet('home', App.Property.findAll());
}
}),
property: Ember.Route.extend({
route: '/property/:property_id',
connectOutlets: function(router, property) {
router.get('applicationController').connectOutlet('property', property);
}
}),
})
});
And here are my findAll() and find() methods:
App.Property.reopenClass({
find: function(id) {
var property = {};
$.getJSON('/api/v1/property/' + id, function(data) {
property = App.Property.create(data.property);
});
return property;
},
findAll: function() {
var properties = [];
$.getJSON('/api/v1/properties', function(data) {
data.properties.forEach(function(item) {
properties.pushObject(App.Property.create(item));
});
});
return properties;
}
});
When I go to a route other than index, for example http://app.tld/#/property/1, the route gets rewritten to http://app.tld/#/property/undefined. Nothing is being passed to the content property of the Property controller. How can I make asynchronous calls in the find() method? Unless I am mistaken, asynchronous calls work fine in the findAll() method, which is the source of my confusion.
This question is similar to Deserialize with an async callback, but I'm using the find() method instead of overriding the deserialize() method.
Thanks in advance.

I found that setting the id property explicitly solves this problem. In your case this would look like this.
find: function(id) {
var user = App.User.create();
$.getJSON('/api/v1/property/' + id, function(data) {
user.setProperties(data.user)
});
user.set("id",id); // <-- THIS
return user;
}
Once your user gets its properties set the view updates as normal. Ember just need the id part before in order to update the URL.
Hope this helps :-)

Here's what you want to be doing. I changed the model to User to make things a little clearer.
In the case of find(), you return a blank model instance that gets it's properties filled in when the AJAX request comes back. The nice thing about Ember's data-binding is that you can display this model in a view immediately and the view will update when the AJAX request returns and updates the model instance.
In the case of findAll(), you return a blank array that gets filled in when the AJAX request comes back. In the same way as find(), you can display this list of models (which at first will be blank) in a view and when the AJAX request returns and fills in the array, the view will update.
App.User.reopenClass({
find: function(id) {
var user = App.User.create();
$.getJSON('/api/v1/property/' + id, function(data) {
user.setProperties(data.user)
});
return user;
},
findAll: function() {
var userList = [];
$.getJSON('/api/v1/properties', function(data) {
var users = data.users.map(function(userData) {
return App.User.create(userData);
});
userList.pushObjects(users);
});
return userList;
}
});

Related

Synchronous data calls in Ember.js

I am trying to do the following when visiting reviews/show (/reviews/:id):
Load two models from the server: One review and one user.
I only have access to the review's id, so I need to load it first, to get the userId
And then, when that has finished loading and I now have the userId, query the user using the userId attribute from the review
Finally, return both of these in a hash so I can use them both in the template
So two synchronous database queries, and then return them both at once in the model hook of the route.
I don't mind if it's slow, it's fast enough for now and I need it to work now.
This is what I've tried and it doesn't work:
reviews/show.js
export default Ember.Route.extend({
model: function(params) {
var user;
var review = this.store.findRecord('review', params.id).then(
function(result) {
user = this.store.findRecord('user', result.get('userId'));
}
);
return Ember.RSVP.hash({
review: review,
user: user
});
}
});
You can do this:
export default Ember.Route.extend({
model: function(params) {
var reviewPromise = this.store.findRecord('review', params.id);
return Ember.RSVP.hash({
review: reviewPromise,
user: reviewPromise.then(review => {
return this.store.findRecord('user', review.get('userId'));
})
});
}
});
The reason why user is undefined is because it hasn't been assigned in your first promise until the review is resolved, but Ember.RSVP.hash has received user as undefined.

Ember JS - Updating / Refreshing Model data From Route Action

This seems to be very simple problem but I can't find any solution for this. I want to refresh the data for unprocessedDailyDataFile from action. I can get the model by modelFor() method. But when I try to use get() and set() method with the model they fails as undefined.
Code for Route
App.AdminRoute = Ember.Route.extend({
model: function(){
return {
companies: this.store.find('company'),
unprocessedDailyDataFiles: this.store.find('unprocessedDailyDataFile')
};
},
actions: {
reloadUnprocessedDailyDataFile: function(){
var model = this.modelFor('admin');
// both properties from the model is accessible here
// model.get() fails
// model.set() fails
}
}
});
For the model to reload, you can use
actions: {
reloadUnprocessedDailyDataFile: function(){
let model = this.get('controller.model'); // for Get and Set
model.get('name');
model.set({ name: 'john'});
}
}

Not fetching correct url issue

I have a backboneJS app that has a router that looks
var StoreRouter = Backbone.Router.extend({
routes: {
'stores/add/' : 'add',
'stores/edit/:id': 'edit'
},
add: function(){
var addStoresView = new AddStoresView({
el: ".wrapper"
});
},
edit: function(id){
var editStoresView = new EditStoresView({
el: ".wrapper",
model: new Store({ id: id })
});
}
});
var storeRouter = new StoreRouter();
Backbone.history.start({ pushState: true, hashChange: false });
and a model that looks like:
var Store = Backbone.Model.extend({
urlRoot: "/stores/"
});
and then my view looks like:
var EditStoresView = Backbone.View.extend({
...
render: function() {
this.model.fetch({
success : function(model, response, options) {
this.$el.append ( JST['tmpl/' + "edit"] (model.toJSON()) );
}
});
}
I thought that urlRoot when fetched would call /stores/ID_HERE, but right now it doesn't call that, it just calls /stores/, but I'm not sure why and how to fix this?
In devTools, here is the url it's going for:
GET http://localhost/stores/
This might not be the answer since it depends on your real production code.
Normally the code you entered is supposed to work, and I even saw a comment saying that it works in a jsfiddle. A couple of reasons might affect the outcome:
In your code you changed the Backbone.Model.url() function. By default the url function is
url: function() {
var base =
_.result(this, 'urlRoot') ||
_.result(this.collection, 'url') ||
urlError();
if (this.isNew()) return base;
return base.replace(/([^\/])$/, '$1/') + encodeURIComponent(this.id);
},
This is the function to be used by Backbone to generate the URL for model.fetch();.
You added a custom idAttribute when you declared your Store Model to be like the one in your DB. For example your database has a different id than id itself, but in your code you still use new Model({ id: id }); when you really should use new Model({ customId: id });. What happens behind the scenes is that you see in the url() function it checks if the model isNew(). This function actually checks if the id is set, but if it is custom it checks for that:
isNew: function() {
return !this.has(this.idAttribute);
},
You messed up with Backbone.sync ... lots of things can be done with this I will not even start unless I want to make a paper on it. Maybe you followed a tutorial without knowing that it might affect some other code.
You called model.fetch() "a la" $.ajax style:
model.fetch({
data: objectHere,
url: yourUrlHere,
success: function () {},
error: function () {}
});
This overrides the awesomeness of the Backbone automation. (I think sync takes over from here, don't quote me on that).
Reference: Backbone annotated sourcecode

EmberJS: initialize nested model with parent model loaded by ajax

I got the following simple ember.js-setup, which works all great
App.Router.map(function() {
this.resource('tourdates', function() {
this.resource('tourdate', { path: ':tourdate_id' });
});
});
App.TourdatesRoute = Ember.Route.extend({
model: function() {
return $.getJSON('http://someapi.com/?jsoncallback=?').then(function(data) {
return data;
});
}
});
App.TourdateRoute = Ember.Route.extend({
model: function(params) {
return tourdates.findBy('id', params.tourdate_id);
}
});
so, pretty simple, whenever i call index.html#/tourdates, i get the data via api. and when I click on a link in this view and call f.e. index.html#/tourdates/1 it just displays the view for its nested child.
This all breaks, when I directly call index.html#/tourdates/1 with the message
DEPRECATION: Action handlers contained in an `events` object are deprecated in favor of putting them in an `actions` object (error on <Ember.Route:ember174>)
Error while loading route: ReferenceError {}
Uncaught ReferenceError: tourdates is not defined
Although he makes the ajax-call to the api and gets the data, he is not able to initialize the nested model
When your App.TourdatesRoute is loaded, all data from the json, will be rendered. And when you click to edit one of these loaded objects, using a link-to for example, ember is smart enough to get the already referenced object, instead of send a new request. So your url will change to: yourhost.com/tourdate/id.
When you direct call this url, it will call the App.TourdateRoute model method. Because doesn't have any pre loaded data. But in your case you have a:
tourdates.findBy('id', params.tourdate_id);
And I can't see in any place the declaration of tourdates.
I recommed you to change your TourdateRoute to TourdateIndexRoute so when transitioning to tourdates the ajax call is performed once:
App.TourdatesIndexRoute = Ember.Route.extend({
model: function() {
return $.getJSON('http://someapi.com/?jsoncallback=?').then(function(data) {
return data;
});
}
});
The TourdatesRoute is called both for TourdateRoute and TourdatesIndexRoute, because it's the parent route of both. So fetching all data in the TourdatesIndexRoute will ensure this is just called when transitioning to tourdates.
In your TourdateRoute you will load just the record needed. Something like this:
App.TourdateRoute = Ember.Route.extend({
model: function(params) {
// retrieve just one data by id, from your endpoint
return $.getJSON('http://someapi.com/' + params.tourdate_id + '?jsoncallback=?').then(function(data) {
return data;
});
}
});
So a direct call to yourhost.com/tourdate/id will just loaded one record.
About your warning message, it happens because in some route you have:
App.MyRoute = Ember.Route.extend({
events: {
eventA: function() { ...},
eventB: function() { ...},
}
});
The events is deprecated and you need to use actions:
App.MyRoute = Ember.Route.extend({
actions: {
eventA: function() { ...},
eventB: function() { ...},
}
});
I hope it helps

Ember Data - TypeError: Object has no method 'eachRelationship'

So, I'm trying to build routes in my Ember application dynamically with data from an API endpoint, /categories, with Ember Data. In order to do this, I'm adding a didLoad method to my model, which is called by the controller and set to a property of that controller. I map the route to my router, and all that works fine. The real trouble starts when I try to set up a controller with a content property set by data from the server retrieved by findQuery.
This is the error:
TypeError {} "Object /categories/548/feeds has no method 'eachRelationship'"
This is the code:
window.categoryRoutes = [];
App.Categories = DS.Model.extend({
CATEGORYAFFINITY: DS.attr('boolean'),
CATEGORYID: DS.attr('number'),
CATEGORYNAME: DS.attr('string'),
CATEGORYLINK: function () {
var safeUrl = urlsafe(this.get('CATEGORYNAME'));
categoryRoutes.push(safeUrl);
return safeUrl;
}.property('CATEGORYNAME'),
didLoad: function () {
var categoryLink = this.get('CATEGORYLINK');
var categoryId = this.get('CATEGORYID');
App.Router.map(function () {
this.resource(categoryLink, function () {
// some routes
});
});
App[Ember.String.classify(categoryLink) + 'Route'] = Ember.Route.extend({
setupController: function(controller, model) {
// source of error
this.controllerFor(categoryLink).set(
'content',
this.store.findQuery('/categories/' + categoryId + '/feeds', {
appid: 'abc123def456',
lat: 39.75,
long: -105
})
);
}
});
}
});
Any 'halp' is appreciated!
Also, if I'm doing this completely wrong, and there's a more Ember-like way to do this, I'd like to know.
I figured this out. I got this error because I was passing in a string instead of a real 'type' from the App.Helpers object to an extract method in some custom RESTAdapter code I had overridden.
The solution is to pass in the corresponding model helper in App.Helpers using my custom type name.
Something like this in the overridden RESTAdapter.serializer.extractMany method:
var reference = this.extractRecordRepresentation(loader, App.Helpers[root], objects[i]);

Categories