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.
Related
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.
I am just starting with ember and trying to do a simple test.
Which, also very simple, got me stuck for some reason and I cant find the answer anywhere.
So I need load data from the server without transition to another route and do it from within a submit action (or any other action for that matter).
I have a simple input form where I type in manually an object ID and
I want it to be loaded say right underneath. Simple enough. Seams to be a three minutes job in angular. Here, I just cant get the hang of communication between route and controller.
So given this little emblem
form submit="submit"
= input type="text" value=oid
button type="submit" Submit
#display
= person
And this route
import Ember from 'ember';
export default Ember.Route.extend({
model: {
person: null
},
actions: {
submit: function() {
var oid = this.controllerFor('application').get('oid');
var person = this.store.find('person', oid);
this.modelFor('application').set('person', person);
}
}
});
This is as far as I could think. I want to click submit with ID of an object and I want that object loaded and displayed in the div#display.
So what am I doing wrong? What is the right way to do it?
First, I don't even know where to put such an action? Controller or route?
If I put it in controller, I don't know how to refresh the model. If I put it in route, I am stuck with the above. Would be also nice to see how to do it if action was placed in the controller.
For simplicity I just do it all in application route, template, controller ...
Thank you
The best place to put your code is on Controller given it responds to UI, so doing that on your controller the code is much more simple.
On this jsfiddle I have put some dummy code which tries to do something what you want to achieve.
//Index Route
App.IndexRoute = Ember.Route.extend({
model: function () {
return ['red', 'yellow', 'blue'];
}
});
//Here my dummy controller.
App.IndexController = Ember.Controller.extend({
oid: 1,
actions: {
submitAction() {
//Here your logic to find record given the input and attach
//the response to the model object as UI is binding to model
//if you add/remove new records they will show up.
//On this example I have added a new object.
this.get('model').addObject('green');
}
}
})
Enjoy!
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:)
Summary:
Controller computed property issue: In one case, I can see all the things getting added but not see the newly added things immediately (jsbin), and in another case I can see the newly added things immediately but the previously added things don't show up (jsbin).
Second Update of Aug 26:
So I was thinking ... I have these two complementary pieces of code. I just need to combine them and achieve perfection, right? Sadly, it failed miserably, as you can see in this jsbin, nothing shows up at all. :(
This is my failed attempt to combine the two RecordArrays:
officelist: function(){
var allrecords = [];
console.log("in oficelist");
// get the record array that shows previously added records
var a_recordarray = App.Office.find({org_id: 'myorgid'});
a_recordarray.forEach(function(record){
allrecords.push(record);
});
console.log(a_recordarray.toString());
console.log(allrecords.length);
// get the record array that shows newly added records
var b_recordarray = App.Office.filter(function(office) {
return office.get('org_id') === 'myorgid';
});
b_recordarray.forEach(function(record){
allrecords.push(record);
});
console.log(b_recordarray.toString());
console.log(allrecords.length);
// return the combination
return allrecords;
}.property('content.#each')
Details:
I have this simple jsbin app that uses a controller computed property in the code to show a list of names to be displayed. The problem is that whenever a new name is added, you have to refresh the page to see it getting displayed.
You can see its code here.
Controller code with computed property:
officelist: function(){
return App.Office.find({org_id: 'myorgid'});
}.property('content.#each')
Route returning a different model:
App.OrganizationRoute = Ember.Route.extend({
model: function() {
return App.Org.find();
}
});
Handlebars:
{{#each officelist}}
<li>{{officename}} </li>
{{/each}}
Constraints: I do need to have the 'org_id' present and I do need to have the route model returning a different model from the model that is displayed.
Update Aug 26: Jonathan has made some progress but please see my comment to his answer as it doesn't solve the problem completely.
Updated Aug 24: Added the complexity that the data to be displayed is different from those returned in the router model. (Also changed ArrayController to ObjectController, but this change has no consequences as ObjectController also has the content property.), below is the old stuff:
Controller code with computed property:
officelist: function(){
return App.Office.find({org_id: 'myorgid'});
}.property('office.#each')
Handlebars:
{{#each officelist}}
<li>{{officename}} </li>
{{/each}}
The problem is that the computed property is cached and will only refresh when office.#each changes. The office property is not defined on that controller, so office.#each is always null. Probably what you want instead is content.#each. So:
officelist: function(){
return App.Office.find({org_id: 'myorgid'});
}.property('content.#each')
Now the page will refresh whenever a new office is added.
If you don't do the find call with the org_id parameter everything works as you want:
officelist: function(){
return App.Office.find();
}.property('content.#each')
jsbin
Change App.OrganizationController's officelist property to this:
officelist: function() {
return App.Office.filter(function(office) {
return office.get('org_id') === 'myorgid';
});
}.property()
The reason is that calling App.Office.find() tries to fetch from the adapter, in your case, localStorage. What you want to do instead is simply pull it out of the store. For this, App.Office.filter() (and in other cases, App.Office.all()) is your friend.
Update:
To also fetch other offices previously saved, fetch them using find(). A place you might do this is when the controller gets initialized in the route's setupController hook.
App.OrganizationRoute = Ember.Route.extend({
model: function() {
return App.Org.find();
},
setupController: function(controller, model) {
this._super.apply(this, arguments);
App.Office.find({org_id: 'myorgid'});
}
});
You don't need to worry about storing the result because any resulting Office records will be loaded into the store, and your App.Office.all() and App.Office.filter() calls will get the updates automatically.
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)