My model relation is defined as follows
App.Person = DS.Model.extend({
name: DS.attr(),
imageUrl: DS.attr(),
bills: DS.hasMany("bill", { async: true}),
});
App.Bill = DS.Model.extend({
description: DS.attr(),
amount: DS.attr(),
person: DS.belongsTo("person")
});
How to fetch bills of all persons inside controller and pass it to controller so that i can display all bills?
One solution is to iterate inside the handlebars template.
But i need to fetch the data in the controller itself and use them as property in template?
App.DashboardController = Ember.ArrayController.extend({
bills: function(){
return /*Bills here*/;
}.property()
});
If I understand you correctly, and the App.Person instance is the model of your DashboardRotue, then I'm not sure what you want is possible with what you have currently.
As a little background info, controllers in Ember.js decorate a model. In layman's terms, it means that all properties come from the controller, even if the controller has to ask the model for them. So if you asked your controller for the bills property, it would see that it didn't have one, and get the one from the model instead. So if all you wanted was the bills for that one person, you wouldn't need to declare anything in your controller at all. You could just do this in your template and it would work fine:
<ul>
{{#each bills}}
<li>{{amount}} - {{description}}</li>
{{/each}}
</ul>
But if I understand you correctly, you seem to wants bills for all people. That is a little trickier, but still possible. If you wanted all of the bills in the whole system (which I think would be the same), the easiest way would be to fetch them all and use them as the model in an ArrayController.
model: function() {
return this.store.find('bill'); // Find all bills
}
You template would then look like this:
<ul>
{{#each}}
<li>{{amount}} - {{description}}</li>
{{/each}}
</ul>
But if you wanted to get the bills for all of the people, you have your work cut out for you. You would need do the same as above, only get all of the persons instead of the bills. You would then need a computed property in your controller to join all of the bills for all of the people. But instead you can't nest #each observers, your property wouldn't update properly. If this is really your use case, I would strong suggest rethinking it. If you really need it, I can help you, but again, it won't be easy.
Related
I am trying to pass a second model to a component. I have several books that are ordered by categories. The categories are chosen by a Multi-Select-Box and I need to pass the categories (which are stored in my db) to the component. I dont need to change the categories I just need an array. Where and how do I request them and pass them on, if i dont want to inject the store into the component? (There is no way to build a many-to-many relationship, the books only have a string-array for the categories).
Right now I am trying to do this:
books/route.js
model() {
this.store.findAll('book');
}
afterModel() {
this.set('categories', this.store.findAll('category');
}
books/template.hbs
{{#each books as |book|}}
{{book-details book=book categories=categories}}
{{/each}}
components/book-detail/template.hbs
<h2>{{book.title}}</h2>
{{#each categories as |category|}}
<p>{{category.name}}</p>
{{/each}}
It doesn't work for the categories like this, so I need to find a way to get them from the store and pass them on to my component.
If it's not necessary for you to fetch categories afterModel, I recommend considering this approach and dropping your afterModel implementation.
model() {
return {
books: this.store.findAll('book'),
categories: this.store.findAll('category')
}
}
Personally, I've moved towards defining a route's model only if there's a single model associated with the route in question (i.e. /books/thug-kitchen). In the case where I have multiple models that apply for a given route I favor using properties.
A better approach would be to use Ember.RSVP.hash, as follows:
model: function() {
return Ember.RSVP.hash({
books: this.store.findAll('book'),
categories: this.store.findAll('category')
});
},
setupController: function(controller, models) {
controller.setProperties(models);
}
This code will work most correctly if the response from the server is delayed. Tyler's approach works, but the transition to the route happens too soon, before the find is complete. I think his approach works because you're using Ember Data, but it doesn't work if it's a more generic Promise. I wish I could explain better, but my own understanding is still very basic.
After that you shouldn't have any problem passing both models on to the component.
Here is what I am trying to understand.
Often times I find myself writing backbone like this:
var CallModel = Backbone.Model.extend({
});
var CallsCollection = Backbone.Collection.extend({
model: CallModel,
url: 'url/to/external/json'
});
It is a very basic example but as you can see, there is nothing really in the model all the data is coming into the Collection via an external url call to a json file that is build from a database.
So whats the purpose of the model? I am sure that I am probably not using backbone.js to its fullest extent which is why I am here asking you guys.
First of all, "there is nothing really in the model all the data is coming into the Collection via an external url call" - this is not true.
Let's assume you've the following:
//Model
var CallModel = Backbone.Model.extend({
defaults: {
cost:0,
duration:0
}
});
(without custom attributes or methods, there is no point in extending the original Backbone.Model)
//Collection
var CallsCollection = Backbone.Collection.extend({
model: CallModel,
url: 'url/to/external/json'
});
And the json data returned from service, probably something like:
//Response
{
callSummary: {
missed: 2,
received: 3,
totalCalls:5
totalDuration: 20
}
calls: [{
id:001,
caller:"Mr.A",
callee:"Mr.B",
cost:1,
duration:5
},{
id:002,
caller:"Mr.X",
callee:"Mrs.Y",
cost:1,
duration:7
},{
id:003,
caller:"Mr.A",
callee:"Mrs.B",
cost:1,
duration:8
}],
//and more additional information from your db
}
Now you populate your collection with data by calling it's fetch method:
CallsCollection.fetch();
Your collection should look something like:
{
models: [{
attributes: {
callSummary: {},
calls: [{},{},{}],
...
},
...
}],
length:1,
url: "url/to/external/json",
...
}
The data will be added to a model's attribute hash. If you don't specify a particular model, as Bart mentioned in his answer, backbone will populate the collection with a Backbone.Model instance: Which is still not much useful - Wew... A collection with single model having entire response data inside it's attributes as it is...
At this point, you're wondering why did I even bother creating a model, and then a collection..?
The problem here is Collections are derived from Arrays, while Models are derived from Objects. In this case, our root data structure is an Object (not an Array), so our collection tried to parse the returned data directly into a single model.
What we really want is for our collection to populate its models from the "calls" property of the service response. To address this, we simply add a parse method onto our collection:
var CallsCollection = Backbone.Collection.extend({
model: CallModel,
url: 'url/to/external/json',
parse: function(response){
/*save the rest of data to corresponding attributes here*/
return response.calls; // this will be used to populate models array
}
});
Now your collection will be something like the following:
{
models: [{
...
attributes: {
...
id:001,
caller:"Mr.A",
callee:"Mr.B",
cost:1,
duration:5
}
},{
...
attributes: {
...
id:002,
caller:"Mr.X",
callee:"Mrs.Y",
cost:1,
duration:7
}
},{
...
attributes: {
...
id:003,
caller:"Mr.A",
callee:"Mrs.B",
cost:1,
duration:8
}
}],
length:3,
url: "url/to/external/json",
...
}
This - is what we want! : Now it is very easy to handle the data: You can make use of the add, remove, find, reset and handful of other collection methods effectively.
You can pass this models array into your templating library of choice, probably with two way bindings: When the respective view for one of the call model changes, the particular model will be updated, events will propagate from your models to the collection, and the particular model will be passed into the handler functions.
You can now call fetch, save, destroy, clear and a lot of other methods with ease on single unit's of data (each model), rather than hurdle with the entire data saved in a single model - which is pretty much useless, you've to iterate through the response data manually and perform CRUD and similar operations by your own, and in most cases: re-render the entire collection view. which is very, very bad and totally unmaintainable.
To conclude: If your data source doesn't return an array of objects, or you don't parse the response and return an array of objects from which n number of models are to be populated - Then defining a collection is pretty much useless.
Hopefully, now you get the idea.
Very helpful source of info:
Backbone, The Primer: Models and Collections
Developing Backbone.js Applications
backbonejs.org
You don't need to specify a model. A Backbone collection will default to using Backbone.Model if you don't specify this option. The following would work equally well if you don't need the models of the collection to be of a particular instance.
var CallsCollection = Backbone.Collection.extend({
url: 'url/to/external/json'
});
Reference
EDIT
In essence, specifying the model option within a collection is just a way to ensure that objects added to this collection will be instances of that particular model class. If the models being added to your collection don't have any custom behaviour outside of what is available to Backbone.Model, you don't need to create and specify a model as Backbone collections will default to using an instance of Backbone.Model as I have already mentioned. If, however, you wanted to ensure that models added to a particular collection were of a particular type and shared customized behaviour (e.g. validations, defaults, etc.), you would create your own model class by extending Backbone.Model and specifying this in the collection. I hope this clears things up for you.
Sounds Weird but this is the way.
Every collection in backbone, must represent a model, so basically a collections is a list of models.
Even if your model has no data, you need to indicate it when you create a Collection.
This is how backbone works for collections.
I have a jsbin: http://jsbin.com/watab/6/
Which is a simplified version of my end goal. Basically I need sort data that is in the hasMany association.
If a User hasMany Books and I want to sort the books for displaying with {{#each book in books}} then how do I sort them?
What I am doing is in the user route during setupController I set the content for the books controller.
this.controllerFor('books').set('content', model.get('books'));
And now in the Users controller I set the needs of course
needs: ['books']
And set an alias
books: Ember.computed.alias('controllers.books.content')
Now I have books controller
App.BooksController = Ember.ArrayController.extend({
sortProperties: ['name'],
sortAscending: false
});
However when I do my {{#each book in books}} it is never sorted.
The jsbin http://jsbin.com/watab/6/ isn't exactly what I described but it employs the basic ideas I have described here.
This is the same jsbin http://jsbin.com/watab/7/ except I changed the sortAscending to true. And I get the same results...
Thanks!
Ok, here is the thing, taking the example of the JSBin here http://jsbin.com/watab/6/, when you're in the 'index' template, your current controller is 'IndexController', that's fine. In that controller you have a property 'users', that's fine; however, that property's content is not sorted, even if you defined the sorting options in the UsersController, that property won't be sorted, why ?, controllers are decorators for your models, so, that code you put for sorting will work whenever you go to the 'users' route, then, the users will be sorted there, that's why it doesn't work in your 'IndexController', because the data is not sorted. To sort it, there are a few ways, but I like this one, in your 'IndexController', add these properties:
userSortProperties: ['name:desc'],
sortedUsers: Ember.computed.sort('users', 'userSortProperties')
and change your template to this:
{{#each user in sortedUsers}}
<div>{{user.name}}</div>
{{/each}}
I hope you get the idea.
I'm brand new to Ember.js, but well-versed in MVC and Backbone (even cocoa and smalltalk), but for some reason, Ember's insistence on its highly-obscured and inconsistent API is shielding me from understanding 1) binding and 2) how that relates to persisting records.
I have a view that has an input. I know that. I have a model that belongs to a controller. I know that. I don't understand how the view knows (or is supposed to know) about the controller, and I don't understand how the model gets the text I type into the view when I'm ready to push it to the backend (ostensibly with .save()). These are supposed to be boilerplate style things that Ember breathlessly abstracts away, but I find I'm having to go through the Ember.js source just to understand what the heck is happening.
I've read the docs, and their examples are so contrived as to be unusable (featuring only the most basic examples, which is pointless when we're talking about "ambitious web apps", which ember purports to be for).
It should be noted that we are using ember-data in house, and this particular route has multiple views, controllers and models.
So let's say you have an input in the index template:
<script type="text/x-handlebars">
{{input type="text" value=name}}
</script>
Ember automatically binds the App.IndexController to this template and the name property of the controller to the value property of the input field. As the value is changed in the input, it'll automatically update in the controller. When you're ready to save it in the model you can access the property in a action.
App.IndexController = Ember.ObjectController.extend({
actions: {
save:function() {
var name = this.get('name');
var model = this.store.createRecord('item');
item.set('name',name);
item.save();
}
}
});
This will trigger a post request to /items to store the item.
If you have a more specific example in mind, I can try to show you how that works.
I've got a sticky situation that I keep on running into: The need for a new instance of a controller inside a handlebars template.
Here is a brief example of my situation. (Please excuse my use of coffeecript)
In Ember, I have a model:
App.Foo = DS.Model.extend
attr: DS.attr()
...
Which I load from an endpoint etc.. And place into an array controller:
App.FooArray = Ember.ArrayController.extend
###*
* Array of App.Foo
* #type {Array}
*/
content:
method: ->
...
Finally, I have an 'instance' controller for this model, which implements further methods (i.e. this is not a singleton controller as would be found at the router level, but a decorator (or proxy) that augments the model with added methods and event handlers):
App.FooController = Ember.ObjectController.extend
###*
* Content
* #type {App.Foo}
*/
content: null
action: ->
...
In handlebars, I want to iterate over items in an App.FooArray:
{{#each myFooArray}}
Hi! My attr is {{attr}}
{{/each}}
etc.. This works splendidly for parameters and such.
However, the trouble starts when I want to use actions (or other properties which would belong to a FooController)
{{#each myFooArray}}
Hi! My attr is {{attr}} <a {{action 'action'}}>Action me!</a>
{{/each}}
Suddenly my actions are not working. That's because the action helper doesn't apply the action to 'this' but rather to a controller higher up, possibly even at the Route level!
So to work around this, I need to pass a target (i.e. a controller):
{{action 'action' target=**********}}
Well, the controller I want is an instance of App.FooController.
Up until now, I've been instantiating controllers inside the model (yuck!):
App.Foo = DS.Model.extend
attr: DS.attr()
...
attrn: DS.attr()
myController: Ember.computed (->
App.FooController.create
content: this
)
and thus iterating as follows:
{{#each myFooArray}}
Hi! My attr is {{attr}} <a {{action 'action' target=myController}}>Action me!</a>
{{/each}}
I know this is bad, but I can't think of a better way. Somebody, please help me see the light!
You can explicitly set the itemController in your each loop.
{{#each myFooArray itemController="foo"}}
Hi! My attr is {{attr}}
{{/each}}
This question poses an important and longstanding question about ArrayControllers, CollectionViews, Models and ObjectControllers.
At the time of writing, my knowledge of the inner workings of Ember was limited. However, I can rephrase my question more concisely as follows:
Given an ArrayController, CollectionView and instance controllers for a model, how can one leverage the itemControllerClass property of the ArrayController to iterate over its content and wrap each item in a unique (i.e. non-singleton) instance of itemController?
Turns out this problem is longstanding and the solution echoes #jeremy-green but I will expand on things here.
First off though: the Ember support thread that encapsulates the problem: https://github.com/emberjs/ember.js/issues/1637
I think the discussion there points very clearly to the need for non-singleton controllers in certain situations.
As well, here is the documentation for ArrayController that indicates the presence of an 'itemController' property on the ArrayController: http://emberjs.com/api/classes/Ember.ArrayController.html#property_itemController
Looking further into the docs, you will also note the presence of an 'lookupItemController' function: http://emberjs.com/api/classes/Ember.ArrayController.html#method_lookupItemController
These functions are for the express purpose of returning the content as an array of Controllers but how?
Well the first requirement is to use the ArrayController directly as the content in a loop. Unfortunately this is where things start to fall apart.
You may think it would be simply the case that a CollectionView can be used:
{{view myCollectionView controllerBinding=myArrayController}}
or
{{view myCollectionView contentBinding=myArrayController}}
But unfortunately this is not the case. More so in the situation where you are rendering a Controller on another Controller's route. CollectionView does not preserve the relationship between the ArrayController and its 'itemControllerClass`
The only way to make this work, as #jeremy-green points out:
{{#each myFooArray itemController="foo"}}
Hi! My attr is {{attr}}
{{/each}}
A more complete example would be:
<ol class="foo-item-list">
{{#each controllers.foo_items}}
<li>{{ view "foo" }}</li>
{{/each}}
</ol>
Wherein App.FooItemsController has either the property itemController or lookupItemController defined.
Unfortunately in this situation we lose the benefits of using the tagName or emptyView properties of CollectionView. Hopefully if an 'ArrayView' is ever created, it will bring the best of both worlds to this situation!