More elegant way to have proxied Ember array property - ember.js

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:)

Related

Push record into local store and update template

I'm using this.store.push to push a record into the store from with the application controller (this action is being called from a socket service that is initialized in the application controller), using ember 2.2.1 I am achieving this like
var newStoreRecord = this.store.push({
data: {
id: id,
type: 'cart',
attributes: newCartItem
}
});
This adds this new item into the store but the template doesn't update to show the new item, I also tried adding something like this
this.get('cart.model').pushObject(newStoreRecord); assuming that I had something like cart: Ember.inject.controller(), at the top of the controller, might have had that one wrong anyway.
In the cart route I have my model being defined as so
model(params) {
this.set('routeParams',params.event_url);
return Ember.RSVP.hash({
event: null,
items: null
});
},
actions: {
didTransition() {
this.store.findRecord('event',this.get('routeParams')).then((result)=>{
this.controller.set('model.event',result);
});
this.controller.set('noItems',false);
this.store.query('cart',{auction_id:this.get('routeParams'),user:this.get('user.user.user_id'),combine:true}).then((result)=>{
if(!result.get('length')){
this.controller.set('noItems',true);
return null;
}else{
this.controller.set('model.items',result);
}
});
},
}
Not sure if I'm having troubles with getting the template to update because I'm not use the model hook? (btw, we're not using the model hook because of the bad performance on android we'd rather load an empty template with a loader and THEN load data rather than the other way around.
I have several thoughts here:
To answer your question specifically, when you set a variable from the store, like you're doing, it will only reference what was in the store at that time. It will not update automatically.
Your best bet is to add two new computed properties to your controller:
items: Ember.computed(function() {
return this.store.peekAll('cart');
}),
// You'll need to flesh this one out further
filteredItems: Ember.computed('items.#each.auction_id', function() {
return this.get('items').filter(...);
})
Reference filteredItems in your template and it should work.
Sidenote, I'd highly recommend refactoring a couple things.
I would use the setupController hook instead of didTransition. It runs after the model hook is complete so will be similar to what you're looking for
You can access the params at any time in the route, so you don't need to save them in the model hook
You don't need to return an a promise in the model hook if you're not doing any async data. Just return the object. You may need even need to do that.
Hope this helps.

store.findAll strange behavior

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
});

Index view not refreshing after receiving updated data from backend

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)

Computed properties during ajax request

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?

Find item in Ember ArrayController

What's the proper way to find item in the Ember.js ArrayController? I have set of contacts in the controller:
App.contactsController = Em.ArrayController.create({
content:[],
});
There are objects in the controller, they are displayed and everything works fine. Then, I want to implement router with serialization/deserialization:
...
deserialize:function (router, params) {
var contact = App.contactsController.find(function(item) {
return item.id == params.contact_id;
});
},
...
However, the find function does not appear to do any iteration. What could be the reason? Is it possible that the Router tries to do the routing before the application calls its ready method? That's the place I fill the controller with data.
EDIT: Well, I have found that router tries to make the transition before I fill my arrayController by the data (in Ember.Application.ready method). Is it possible to "delay" routing after the data is properly set?
var contact = App.contactsController.filter(function(item) {
return item.id == params.contact_id;
});
I think you can run Application.initialize() when router has been set. You can use observer to detect data set.
dataChanged: function() {
console.log(this.get('content.length'));
// before emberjs 1.0pre
// console.log(this.getPath('content.length'));
}.observes('content')
The problem was actually caused by insertind data into arrayController after the Router did its deserialization. Putting it before App.initialize() solved the problem.
the correct answer is:
var contact = App.contactsController.content.find(function(item) {
return item.id == params.contact_id;
});
It will not return an item if you don't point to the content array.