I'm having trouble with a computed property.
It's a complex manipulation on an ArrayController. The problem is, Ember attempts to calculate it before the data has loaded. For example, part of it is
var counts = this.getEach('hours').forEach(function(hours) {
var d = hours.find(function(_hour) {
return +(_hour.date.substring(11, 13)) === 10;
});
return d.count;
});
I get an error because this.getEach('hours') returns something like
[ Array[24], undefined ]
while the AJAX request is loading, so the code breaks.
I'm sure others have run into this before - what's the solution?
Update: Here's how I get the data. When a user clicks a month in a view, I pass the clicked month's id to my MonthsController. It has a toggleMonth method:
App.MonthsController = Ember.ArrayController.extend({
toggleMonth: function(id) {
var month = App.Month.find(id),
index = this.indexOf(month);
if (index === -1) {
this.pushObject(month);
} else {
this.removeAt(index);
}
}
});
App.Month.find(id) sends the correct AjAX request + the data returns, but perhaps this is not the correct way to populate the months controller.
Also, this is happening within the IndexRoute (i.e. I have no separate route for the MonthsController. So, I never specify a model hook or setupController for the MonthsController.
The general approach to this problem is promises: asynchronous requests immediately return a promise, which is basically a promise of value, which can be resolved later down the line. All Ember models are promises behind the scenes. See ember models as promises, and How are Ember's Promises related to Promises in general, and specifically jQuery's Promises?
Could you explain the context of the first block of code? What is this in this.getEach('hours').forEach and when is that block executed?
Related
I thought I understood how the store.findAll and the Promise.All works. But I have run into some strange behavior.
I have two findAll(), but only one of them is fullfilled after the Promise.All gets into the then()-part.
See this code
export default Route.extend({
model() {
var self = this;
return Ember.RSVP.Promise.all([
self.store.findAll('contact'),
self.store.findAll('message')
]).then(function(values) {
var contacts = values[0];
var messages = values[1];
var numberOfContacts = contacts.get('length'); // This is 39 as expected.
var numberOfMessages = messages.get('length'); // This is 0. Expected is 1.
...
There must be something different with messages and contacts, but I cannot figure out what it is. They have very similar models and they have very similar backend API handling. From the network traffic I can see that a message object is returned and if I call (later in the code, after the model hook):
store.peekAll('message');
I get the message object I expect.
I use ember 3.0.0
I figured it out. It is due to a strange behavior of findAll() in Ember.
FindAll() will return immediately with the elements that was already present in the store. Later, when more objects have been retrieved from the server, the store is updated, but the promise of the findAll()-call is long gone.
To work around this strange behavior, there is an option to the findAll() method.
{reload: true}
It is used this way:
return self.store.findAll('message', { reload: true }).then(messages => {
var messageLength = messages.get('length');
...
With this reload-option set, findAll() and promises work as expected.
Ember - v1.7.0
Ember Data - v1.0.0-beta.10
I created a modal component using zurb foundation 5 CSS framework reveal features, though all works well, am unable to save data captured from the form in controller save action.
Controller which handles on save button execution
App.PersonModalController = Ember.ObjectController.extend({
actions: {
close: function() {
return this.send( 'closeModal' );
},
save:function() {
this.get('model').save();
}
}
});
The issue am facing is that the this.get('model').save() is not working and data is not been posted to restful backend.
Am not sure exactly how to go about storing the data captured from the form, when I console.log( this.get('model') ); it appears to be a proper model object with all the bells and whistles.
I tried obtaining the store to add model to it but that doesn't work too.
A. Addendum
After searching around I came across a number of Stack Overflow questions relating to this.get('model').save() it appears it doesn't quite work as expect, perhaps based on context.
difference-between-model-save-versus-model-getstore-commit
ember-js-how-to-save-a-model
save-record-of-model-is-not-working-in-ember-data-1-0-0-beta-3
When I change code to the following:
App.PersonModalController = Ember.ObjectController.extend({
actions: {
close: function() {
return this.send( 'closeModal' );
},
save:function() {
var person = this.store.createRecord('person',{firstName:firstName,lastName:lastName});
person.save();
}
}
});
It POSTs data correctly to back-end and saves, I however believe there must be a better way, cause if you have a form with say 50 fields, you won't want to manually set each attribute.
After careful inspection, though posting occurs, the data posted is empty.
I would try to continue your save method as in Bart's answer to the ember-js-how-to-save-a-model question.
person.save().then(function() {
// SUCCESS
}, function() {
// FAILURE
});
and in those methods I would console.log() the results.
I would imagine it has something to do with the promise aspect of the save functionality.
here is the issue i am trying to resolve.
I am trying to retrieve contact record from store via query string params (fields: id and tab). i can see the contact model being retrieved in the browser console .
since find operation is being executed on query params the response is array of one contact record.
that's why in the controller code beneath i am extracting the contact model using contact.get('firstObject').
however nothing gets rendered in the browser as before this whole operation is done the template gets rendered.
I dont understand this behavior. since i am wrapping this operation in RSVP promise call.
till this promise is returned from this model hook, Ember.js should block until the promise is resolved . please let me know what is going wrong here.
export default Ember.ObjectController.extend({
model:function(){
var self = this;
return new Ember.RSVP.Promise(function(resolve, reject) {
self.store.find('contact',{id:1, tab: "contactInfo"}).then(function(contact) {
contact.get('firstObject');
});
});
}.property('model')
});
You were almost there. Apart from some redundant code, you should of course return your firstObject. This should work:
model:function(){
return this.store.find('contact',{id:1, tab: "contactInfo"}).then(function(contact) {
return contact.get('firstObject');
});
Promises are ember's approach on handling asynchronous logic and the whole goal is not to block the whole application. When the data gets in, the template will be updated.
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)