I want to render 2 lists of items, both templates that each over the model.
I tried to render the main template first and then the nested ones into some named outlets of the main template, which works.
But when I try to give the nested ones a different model (they all use different model arrays), I get an error:
The value that #each loops over must be an Array.
You passed '<DS.PromiseArray:ember451>' (wrapped in (generated homepage controller))
Here is the code:
renderTemplate:function(){
this.render('homepage');
this.render('roomlist',{
'into':'homepage',
'outlet':'roomlist',
'model':this.store.find('room')
});
this.render('activitylist',{
'into':'homepage',
'outlet':'activitylist',
'model':this.store.find('activity')
});
}
Edit:
Alternative idea I had, was to use {{render "roomlist" rooms}} after this.set("rooms", this.store.find("room"); in the model-hook instead of the renderTemplate-hook. But it threw almost the same error:
The value that #each loops over must be an Array.
You passed (generated roomlist controller)
You should resolve the models you will use in the model hook, and then you can use them in your templates much easier:
something like this:
model: function() {
return Ember.RSVP.hash({
rooms: this.store.find('room'),
activities: this.store.find('activity')
});
},
setupController: function(controller, model) {
this.set('rooms', model.rooms);
this.set('activities', model.activities);
controller.set('model', model.rooms);
},
renderTemplate: function(){
this.render('homepage');
this.render('roomlist',{
'into':'homepage',
'outlet':'roomlist',
'model':this.get('rooms')
});
this.render('activitylist',{
'into':'homepage',
'outlet':'activitylist',
'model':this.get('activities')
});
}
Related
I have a todo list.
The main template has an input for entering new todos and an outlet.
The outlet is for the todos.index template which displays each todo. I have made them srtable using jquery sortable. I sort them using a model property.
todos route:
model: function(){
return this.store.filter("todo", {}, function(todo){
if(todo.get("user") !== null && parseInt(todo.get("user").get("id")) === user_id)
return todo;
});
todos index route:
model: function(){
return this.modelFor('todos').sortBy('idx');
}
controllers:
App.TodosController = Ember.ArrayController.extend();
App.TodosIndexController = Ember.ObjectController.extend();
But when I add this .sortBy() method my returning array of objects is no longer live and any new todos i create arent added to the template. (they do get created and are in ember data and in my db, but they just arent being added to the template) - When you do sortBy the live array of ember data gets copied into an immmutable regular array.
If I leave out the sortBy() my new objects populate my template just fine.
any ideas of how to sort an array but keep it enumerble or how to refresh the template?
sortProperties is not working - I dont know why. My list of todos is populated by the objectController.
EDIT -
My problem was my own fault. I thought I had this
App.TodosIndexController= Ember.ObjectController.extend();
But what I actually had was:
App.TodoController = Ember.ObjectController.extend();
This left me without an explicit controller to do the sorting for me, so for me the solution was to create a new arrayController:
App.TodosIndexController= Ember.ObjectController.extend();
and use that to do the sorting. Though it wasnt the solution to my problem, my issue was my own obliviousness, it is the right way to sort items so I am going to mark #Kingpins answer as correct.
Make your controller sorted, and iterate over the controller (not the model) in your template.
App.TodosIndexController = Ember.ArrayController.extend({
sortProperties: ['idx'],
sortAscending: true
});
return your model to just the modelFor
model: function(){
return this.modelFor('todos');
}
Or you could create a computed property on the controller and watch the model for updates, updating the computed property
App.TodosIndexController = Ember.ArrayController.extend({
sortedTodos: function(){
return this.get('model').sortBy('idx');
}.('model.[]')
});
and again, return your model to just the modelFor
model: function(){
return this.modelFor('todos');
}
The other way to manually update the template, is to just get the model for TodosIndexController and manually use pushObject to add the new todos to it. It's just an array of items.
I have a Route which loads multiple models:
App.AppRoute = Ember.Route.extend({
model: function() {
return Ember.RSVP.hash({
models1: this.store.find('model1'),
models2: this.store.find('model2'),
models3: this.store.find('model3'),
})
}
});
I want to extend the ArrayController for this page:
App.AppController = Ember.ArrayController.extend();
And the error is thrown:
Error: Assertion Failed: ArrayProxy expects an Array or Ember.ArrayProxy, but you passed object
My question is how to write a controller which can handle multiple model objects.
Really an array doesn't make since. You have 3 different arrays.
So it would be an object controller, and you can access each item as an array in the application template or wherever it's appropriate.
App.AppController = Ember.ObjectController.extend();
I am trying to access one of two models in a controller that uses needs on a sibling controller. My router looks like the following:
App.Router.map(function() {
this.route('login');
this.route('mlb.lineups', {path: 'tools/mlb/lineups'})
this.resource('mlb.lineups.site', { path: 'tools/mlb/lineups/site/:site_id' });
});
The mlb.lineups route definition looks like the following:
App.MlbLineupsRoute = Ember.Route.extend({
model: function() {
var self = this;
return Ember.RSVP.hash({
sites: self.store.find('site')
})
},
setupController: function(controller, models) {
controller.set('model', models.get('sites'));
},
afterModel: function(models) {
var site = models.sites.get('firstObject');
this.transitionTo('mlb.lineups.site', site);
}
});
The reason I am using Ember.RSVP.hash({}) here is I plan on adding another model to be retrieved after I retrieve the site model.
Now in my MlbLineupsSiteController I am trying to access the sites model with the following:
App.MlbLineupsSiteController = Ember.ArrayController.extend({
needs: "mlb.lineups",
sites: Ember.computed.alias("controllers.models.sites")
});
This is the error I'm getting in my Ember console: needs must not specify dependencies with periods in their names (mlb.lineups)
What's the best way to make the sites model from the MlbLineups controller available in my MlbLineupsSiteController?
Note:
#NicholasJohn16's answer isn't valid anymore. It always gives an error that controller couldn't be found. Generally you should also never use needs property and always use Ember.inject.controller if you have to make your controllers dependent on each other. I'd also recommend using services instead of dependencies between controllers. It's easier to maintain code which contains communication between controllers through services, than controller directly accessing other controller's properties. You might not always be aware of such access, and using services gives you another layer of security.
Solution:
Tested in Ember.js 1.10.0-beta.4. Use following code in Controller to reference nested controller in needs:
needs: ['classic/about']
Then you can access it later using:
const aboutController = this.get('controllers.classic/about');
const aboutProperty = aboutController.get('customProperty');
Works as expected. Basically you need to replace dots with slashes.
It should be:
needs:" MlbLineupsSite "
Basically, the name of the controller you want to include, minus the word controller.
Everything else you posted should work.
I have a route that has two models associated with it as shown below:
App.IndexRoute = Ember.Route.extend({
model: function() {
return Ember.RSVP.hash({
sites: this.store.find('site'),
songs: this.store.find('song')
})
},
Now later on, I need to be able to retrieve the first object in the sites model in order to do a transition I'll show below. I figured I can set the models using setupController, but when dealing with multiple models as depicated above, I'm not sure how to fill this part in:
setupController: function(controller, ???) {
controller.set('model1', ???);
controller.set('model2', ???);
}
And finally, I'd like to be able to retrieve the first object in model1 (it's multiple instances of site as described above)
afterModel: function() {
firstRecord = this.('sites').objectAt(0);
this.transitionTo('site', firstRecord.id);
}
It's also possible that I'm not designing my application properly. sites in this case is a component I built that displays different sites within a few different controllers. The controllers are dependent on this component in that they need to know which site is selected in order to do their own thing. So in controllers that need access to the component, I do something like:
{{site-nav sites=sites}}
Where site-nav is my component. It needs its own model, as does the controller itself.
First, you're going to need to modify your model hook slightly, to make sure you stay in the right scope:
model:function(){
var self = this;
return Ember.RSVP.hash({
sites: self.store.find('site'),
songs: self.store.find('song')
})
}
To get the different models in setupController, you just access it from the second parameters, like this:
setupController:function(controller,models) {
controller.set('sites',models.sites);
controller.set('songs',models.songs);
}
afterModel provides two parameters, this first being the resolved model for your route, so you'd do it something like this:
afterModel:function(models){
var site = models.sites.get('firstObject');
this.transitionTo('site',site);
}
I understand how to get a collection together, or an individual model. And I can usually get a model's data to display. But I'm not clear at all how to take a collection and get the list of models within that collection to display.
Am I supposed to iterate over the collection and render each model individually?
Is that supposed to be part of the collection's render function?
Or does the collection just have it's own view and somehow I populate the entire collection data into a view?
Just speaking generally, what is the normal method to display a list of models?
From my experience, it's the best to keep in your collection view references to each model's view.
This snippet from the project I'm currently working on should help you get the idea better:
var MyElementsViewClass = Backbone.View.extend({
tagName: 'table',
events: {
// only whole collection events (like table sorting)
// each child view has it's own events
},
initialize: function() {
this._MyElementViews = {}; // view chache for further reuse
_(this).bindAll('add');
this.collection.bind('add', this.add);
},
render: function() {
// some collection rendering related stuff
// like appending <table> or <ul> elements
return this;
},
add: function(m) {
var MyElementView = new MyElementViewClass({
model: m
});
// cache the view
this._MyElementViews[m.get('id')] = MyElementView;
// single model rendering
// like appending <tr> or <li> elements
MyElementView.render();
}
});
Taking this approach allows you to update views more efficiently (re-rendering one row in the table instead of the whole table).
I think there are two ways to do it.
Use a view per item, and manipulate the DOM yourself. This is what the Todos example does. It's a nice way to do things because the event handling for a single model item is clearer. You also can do one template per item. On the downside, you don't have a single template for the collection view as a whole.
Use a view for the whole collection. The main advantage here is that you can do more manipulation in your templates. The downside is that you don't have a template per item, so if you've got a heterogeneous collection, you need to switch in the collection view template code -- bletcherous.
For the second strategy, I've done code that works something like this:
var Goose = Backbone.Model.extend({ });
var Gaggle = Backbone.Collection.extend({
model: Goose;
};
var GaggleView = Backbone.View.extend({
el: $('#gaggle'),
template: _.template($('#gaggle-template').html()),
render: function() {
$(this.el).html(this.template(this.model.toJSON()));
}
};
var g = new Gaggle({id: 69);
g.fetch({success: function(g, response) {
gv = new GaggleView({model: g});
gv.render();
}});
The template code would look something like this:
<script type="text/template" id="gaggle-template">
<ul id="gaggle-list">
<% _.each(gaggle, function(goose) { %>
<li><%- goose.name %></li>
<% }); %>
</ul>
</script>
Note that I use the _ functions (useful!) in the template. I also use the "obj" element, which is captured in the template function. It's probably cheating a bit; passing in {gaggle: [...]} might be nicer, and less dependent on the implementation.
I think when it comes down to it the answer is "There are two ways to do it, and neither one is that great."
The idea of backbone is that view rendering is event driven.
Views attach to Model data change events so that when any data in the model changes the view updates itself for you.
What you're meant to do with collections is manipulate a collection of models at the same time.
I would recommend reading the annotated example.
I've also found this a confusing part of the Backbone framework.
The example Todos code is an example here. It uses 4 classes:
Todo (extends Backbone.Model). This represents a single item to be todone.
TodoList (extends Backbone.Collection). Its "model" property is the Todo class.
TodoView (extends Backbone.View). Its tagName is "li". It uses a template to render a single Todo.
AppView (extends Backbone.View). Its element is the "#todoapp" . Instead of having a "model" property, it uses a global TodoList named "Todos" (it's not clear why...). It binds to the important change events on Todos, and when there's a change, it either adds a single TodoView, or loops through all the Todo instances, adding one TodoView at a time. It doesn't have a single template for rendering; it lets each TodoView render itself, and it has a separate template for rendering the stats area.
It's not really the world's best example for first review. In particular, it doesn't use the Router class to route URLs, nor does it map the model classes to REST resources.
But it seems like the "best practice" might be to keep a view for each member of the collection, and manipulate the DOM elements created by those views directly.