I have the following code in a controller:
ingredients: function() {
var self = this;
this.store.findAll('ingredient').then(function() {
var ingredients = self.get('model').get('ingredientsWithQuantities').map(function(item) {
return {
name: self.store.peekRecord('ingredient', item.ingredientId).get('name'),
quantity: item.quantity
};
});
self.set('ingredients', ingredients);
});
}.property('model.ingredientsWithQuantities')
It's a computed property related to one of the routes. It works fine if 'ingredients' model is loaded somewhere earlier. But if it's loading for the first time, when then function is called there is no data in the store.
I was expecting that findAll would call then only after data was fetched. How can I handle this?
UPDATE:
When I add shouldReloadAll() { return true; } to my application RestAdapter it works fine except one thing. It actually reloads always even if data exists. But I need just one request of this type during user's session.
It is possible to achieve the next behavior?
when data doesn't exist in the store - then load data and call function after it
when data exists in the store - then just call function
this.store.findAll('ingredient')will return all ingredients in the store and then it will update it with a request to the backend, or that's how I think it works but I'm not sure why it isn't working like that.
A solution is to use this.store.query which will make a request to the backendfor sure.
Example:
return this.store.query('ingredient', { filter: { } }).then(function(result) {
// do something with the result
});
Related
I have a model with a Fixtures data set, so no backend involved in here. For the model, I have 22 data records. When I query it for the first time in my IndexRoute, all 22 data records are returned. No problem here.
When I leave the route, and come back later, the model hook of my IndexRoute is called again, but this time the same query does not return data.
My Model hook looks like:
model: function () {
var placeId = 0;
console.log('Index Route: Model Hook');
console.log('Getting hints for place ' + placeId);
this.get('store').find('hint', { place: placeId })
.then(
function (hints) {
console.log('Found hints', hints.get('content'));
}
);
return this.get('store').find('hint', { place: placeId });
}
As you can see, for demo purposes I always query hints with place id equal to zero. As already said, the first time it returns the data (and I can see the data in the Chrome Ember Inspector), but the second time I enter this route does not return the data (which I know is out there).
Edit:
My hint model looks basically like
App.Hint = DS.Model.extend({
title: DS.attr('string'),
// some basic boring attributes
place: DS.belongsTo('place', { async: true }) // Association with my Place Model
});
App.Place = DS.Model.extend({
title: DS.attr('string'),
// some more attributes
hints: DS.hasMany('hint', { async: true })
});
So the query {place: placeId} simply gets all those hints who have an association with a specific place. The problem is not that the query does not work - it works the first time the Index Route is triggered (and it works as I expect it to work). The problem is that all subsequent calls of index route, and all other locations that are trying to access hints, do not work anymore and always return an empty set.
Finally I found the answer. The problem seems to be related to finding the hint records through the belongsTo association with my places.
Anyway, I found this post Find record from belongsTo association in Ember.js and this is how the actual solution looks like:
model: function () {
var placeId = 0;
return this.store.find('place', placeId)
.then(function (place) {
return place.get('hints');
})
.then(function (hints) {
return hints;
});
},
I am a little confused about what { place: placeId } is supposed to be doing, since i am not sure if fixtureAdapter can mimic server queries (never tried).
That said however, if you want your route to always return all the 'hints' in your fixture data, all you should need to do is this:
return this.store.find('hint');
note: you only need to call once.
if that still does not work. try posting what your fixture data and adapter looks like.
I am attempting to save an Ember Data DS.Model after it's been updated, but when I call myModel.save(), I'm finding that Ember Data is sending the original, non-updated model instead of the updated one. I'm trying to understand why this is happening and what I need to do differently.
Here are some details. First, I have two models:
/models/OrgUser.js:
DS.Model.extend({
...
orgPerson: DS.belongsTo('org-person', { inverse: 'org-user', async: true, embedded: 'always' }),
});
Note that I am using a customized RESTSerializer (see below), so the only use of embedded: 'always' is how my custom RESTSerializer handles it.
/models/OrgPerson.js:
DS.Model.extend({
...
orgUser: DS.belongsTo('org-user'),
})
To persist these models, I'm using the RESTAdapter. In an attempt to generate a single JSON request to my API that contains both models above, I've made a single customization to the adapter. I don't think this is affecting anything, but just in case I'm missing something, here it is:
/serializers/application.js:
DS.RESTSerializer.extend({
serializeBelongsTo: function(record, json, relationship) {
var key = relationship.key;
key = this.keyForRelationship ? this.keyForRelationship(key, 'belongsTo') : key;
var data = record.get('data');
if (relationship.options.embedded && relationship.options.embedded === 'always') {
json[key] = data[relationship.key] ? data[relationship.key].get('data') : null;
}
else {
json[key] = data[relationship.key] ? data[relationship.key].get('id') : null;
}
if (relationship.options.polymorphic) {
this.serializePolymorphicType(record, json, relationship);
}
}
})
With that setup, I have a template where I update the orgPerson properties. I can confirm these are bound properties because updating their input updates their display on another part of the template in real-time. I then call an action on my controller, and within that action do the following:
/controllers/my-page.js:
export default Ember.ObjectController.extend( FormMixin, {
actions: {
submitForm: function() {
...
this.get('model') // Chrome console shows that _data.orgPerson._data.firstName has the (incorrect) old property
this.get('model').serialize() // returns (incorrect) old firstName
this.get('orgPerson.firstName') // returns (correct) updated firstName
this.get('orgPerson').get('firstName') // returns (correct) updated firstName
...
}
}
});
Any idea why I am getting two different versions of the same model? How can I serialize the correctly updated model? Thanks for any input!
SOLUTION:
Thanks (again!) to #kingpin2k, I have resolved this issue. Here are the steps I took:
My serializer was in fact the problem, and using Ember's old preserved data. I replaced the line data[relationship.key].get('data') with the line data[relationship.key].serialize() and this was fixed.
I then ran into another issue, which was that if I edited my record, did NOT save it, and then went back to my list of records, the list still showed the edit. My first thought was that I needed to update my list page's array model to show only the latest content, but there didn't appear to be any Ember facilities for this.
So I ultimately solved this by using the following code in my route. Note that because orgPerson is async: true I had to wrap my model in a promise. Note also that I had to directly call model.orgPerson versus just model.
Updated route:
actions: {
willTransition: function( transition ) {
this.controller.get('model.orgPerson').then( function( value ) {
if ( value.get('isDirty') ) {
value.rollback();
}
});
}
}
Going forward, I just want to call this.controller.get('model').rollback(), so I'm going to write a util function that traverses eachRelationship and then individually calls rollback() on any of the objects. Whew, a lot of subtlety to get this working right.
Ember Data stores the original values in the data obj. It stores modified values in _attributes obj. During a save it moves _attributes obj to inFlightAttributes obj, then after the save is complete it merges them from inFlightAttributes to data. All of this is so you can rollback your record.
When you define a property as attr it hooks up the magical get where it first checks _attributes, then inFlightAttributes, then data and returns that property's result.
function getValue(record, key) {
if (record._attributes.hasOwnProperty(key)) {
return record._attributes[key];
} else if (record._inFlightAttributes.hasOwnProperty(key)) {
return record._inFlightAttributes[key];
} else {
return record._data[key];
}
}
https://github.com/emberjs/data/blob/v1.0.0-beta.8/packages/ember-data/lib/system/model/attributes.js#L267
In your case, Ember Data doesn't know you are saving that record, and you are manually grabbing the old properties from the data obj. You'd either need to manually merge _attributes to data or trick Ember Data into thinking you'd saved it.
I have the following array:
eligible_students: function() {
self = this;
this.store.find('user', App.CurrentUser.get('id')).then(function(user) {
console.log(user);
var students = user.get('students').then(function(students) {
console.log(students);
var results = user.get('students').map(function(item) {
return {student: item, queued: false};
});
console.log(results);
self.set('eligible_students', results);
});
}
);
return [];
}.property('App.CurrentUser.id')
Everything works fine, but there's a stutter when this is rendered, since I'm adding the results after returning an empty array. Is there a way to do this that inherently takes advantage of Ember Promises? Or some other beautiful functionality?
I'm happy to provide more information on request :)
If you want to wait for the page to render until the users are loaded, then you should use the model hook in the router. You could use Ember.RSVP.hash (https://stackoverflow.com/a/20523510/1234490) to return multiple models if necessary. Then in the controller you could add a function eligibleStudens. I think it should be in the controller since it is along the lines of a completedTodos function for example.
This way you won't notice the stuttering. Lemme know if it works:)
I am testing my application, so I am doing the following:
I show an index view (#/locators/index), of Locator objects, which I initially load with App.Locator.find();
I modify the backend manually
Manually (with a button/action) I trigger a refresh of the data in the ember frontend, without changing the route. I do this with App.Locator.find().then(function(recordArray) {recordArray.update();});. I see via console logging that a list request is sent to the backend, and that the up-to-date data is received. I assume this is used to update the store.
BUT: The view does not update itself to show this new data
Why does the view not get automatically updated when the store receives new data? Isn't that the whole point of the data binding in Ember?
If I now do the following:
Open any other route
Go back to the locators index route (#/locators/index)
Ember sends a new request to list the locators
The index view is shown, with the correct data (since it was already in the store?)
New data is received
(I am not 100% sure that 4 and 5 happen in that order, but I am quite certain)
So, my impression is that the data is properly updated in the store, but that somehow a full re-rendering of the view is needed to display this new data, for example by leaving and re-entering the route. Is this true? Can I force this re-rendering programmatically?
Ember changes view data when the underlying model is changed by the controller(Which is binded to the view)
(Only when the state of the application changes(url changes) router hooks are called)
Your problem could be solved when you do this.refesh() inside your route by capturing the action triggered by your view.
App.IndexRoute = Ember.Route.extend({
actions: {
dataChanged: function() {
this.refresh();
}
},
//rest of your code goes here
});
for this to work your handlebar template which modifies the data shoud have an action called dataChanged
example :
Assume this action is responsible for changing/modifying/deleting the underlying data
<button {{action 'dataChanged'}}> Change Data </button>
Refresh method actually does a model refresh and passes it to the corresponding controller which indeed changes the view.
There a couple of things that come to mind you could try:
If you are inside of an ArrayController force the content to be replaced with the new data:
this.replaceContent(0, recordArray.get('length'), recordArray);
Or try to call reload on every single record trough looping the recordArray:
App.Locator.find().then(function(recordArray) {
recordArray.forEach(function(index, record) {
record.reload();
}
}
And if the second approach works, you could also override the didLoad hook in your model class without having to loop over them one by one:
App.Locator = DS.Model.extend({
...
didLoad: function(){
this.reload();
}
});
If this works and you need this behaviour in more model classes consider creating a general mixin to use in more model classes:
App.AutoReloadMixin = Ember.Mixin.create({
didLoad: function() {
this._super();
this.reload();
}
});
App.Locator = DS.Model.extend(App.AutoReloadMixin, {
...
});
App.Phone = DS.Model.extend(App.AutoReloadMixin, {
...
});
Update in response to your answer
Handlebars.registerHelper is not binding aware, I'm sure this was causing your binding not to fire. You should have used Handlebars.registerBoundHelper or simply Handlebars.helper which is equivalent:
Handlebars.helper('grayOutIfUndef', function(property, txt_if_not_def) {
...
});
Hope this helps.
Somehow this seems to be due to the fact that I am using custom handlebar helpers, like the following:
Handlebars.registerHelper('grayOutIfUndef', function(property, txt_if_not_def) {
// HANDLEBARS passes a context object in txt_if_not_def if we do not give a default value
if (typeof txt_if_not_def !== 'string') { txt_if_not_def = DEFAULT_UNDEFINED_STR; }
// If property is not defined, we return the grayed out txt_if_not_def
var value = Ember.Handlebars.get(this, property);
if (!value) { value = App.grayOut(txt_if_not_def); }
return new Handlebars.SafeString(value);
});
Which I have been using like this:
{{grayOutIfUndef formattedStartnode}
Now I have moved to a view:
{{view App.NodeIconView nodeIdBinding="outputs.startnode"}}
Which is implemented like this:
App.NodeIconView = Ember.View.extend({
render: function(buffer) {
var nodeId = this.get('nodeId'), node, html;
if (nodeId) {
node = App.getNode(nodeId);
}
if (node) {
html = App.formattedLabel.call(node, true);
} else {
html = App.grayOut(UNDEFINED_NODE_NAME);
}
return buffer.push(html);
}
});
I am not sure why, but it seems the use of the custom handlebars helper breaks the property binding mechanism (maybe my implementation was wrong)
This is code illustration:
http://jsbin.com/uzapuy/1/edit
I want to be able to access test by URL like so: http://jsbin.com/uzapuy/1#/test/2
Because entrie may already present in client side, I want to check that first and only if it missing fetch from server side.
Is that possible without DS.Store?
In your TestRoute, can you not do something like:
App.TestRoute = Ember.Route.extend({
model: function(params) {
// Find your controller that has the "fetch" method.
var testController = this.controllerFor('test');
// Check if there's an existing model with this ID.
var existingModel = testController.find('id', params.id);
// Determine if we found a model or not.
if (existingModel) {
// If we found an existing model, then we can set this as the model.
return existingModel;
}
// Otherwise we'll fetch it from the server.
return testController.fetch(params.id);
}
});
Also, bear it in mind that in your code, the test variable is undefined:
var tests = App.Test.create({id: id, name: 'fetched ' + id});
self.set('content', test);
You're after tests, I assume.