I have a Model called Course that has an array variable called users. Is there a way to just limit the results of, let say, IDs from users rather than the {{#each}} rendering every single id?
Such that
<script type="text/x-handlebars" data-template-name="course">
{{#each user in users 5}}
{{user.name}}
{{/each}}
</script>
Will show the first 5 IDs rather than every single id stored in users?
Here is the Ember-data model that I am using
App.Course = DS.Model.extend({
//Attributes
name: DS.attr('string'),
description: DS.attr('string'),
//Relations
users: DS.hasMany('App.User'),
});
I tried multiple times at creating a registerHelper, but where it always seems to go wrong is when an 'a in b' is present.
Any help or ideas would be appreciated. Thank you very much!
Rather than modifying the #each helper you could take a different approach to show only a limited amount of items when looping over the array with the #each helper.
See this for a possible implementation:
App.IndexRoute = Ember.Route.extend({
model: function() {
return [
Ember.Object.create({id:1, name:'Red'}),
Ember.Object.create({id:2, name:'Green'}),
Ember.Object.create({id:3, name:'Blue'})
];
}
});
App.IndexController = Ember.ArrayController.extend({
limitedContent: function() {
return this.get('content').splice(0, 2);
}.property('content')
});
The .property('content') defines a binding to the controller's content and takes care that the #each helper rerenders when the content changes.
And then in your template you loop over the limitedContent rather then the content:
<script type="text/x-handlebars" data-template-name="course">
{{#each color in limitedContent}}
{{color.name}}
{{/each}}
</script>
And here a working jsbin that show the concept mentioned.
Hope it helps
I was using the method in the accepted method, but ran into an issue with it.
The problem lay in that using splice in ember modifies the underlying data:
filtered: function() {
return this.get('content').splice(0,2);
}.property('content'),
splice removes the elements from the underlying content.
A better method is to use Ember's built-in slice function, changing the above code into
filtered: function() {
return this.get('content').slice(0,2);
}.property('content'),
And that's it, now it doesn't modify the underlying array, because slice returns a new array and leaves the underlying data untouched.
JSBin showing that splice modifies underlying data:
http://emberjs.jsbin.com/ridixawimugo/1/
JSBin with the fixed solution:
http://emberjs.jsbin.com/voceye/1/
Related
Say I have two objects in my application: 1) Topic, 2) Categories.
Topic has a route /topic/:topic_id
Categories doesn't have a URL route. It contains the list of all available categories that topics can be in.
On the page for Topic, I would like to show a list of all categories, while highlighting the category that the topic is a part of (the 'active' category)
Original Solution
I had originally solved this with a component as follows (a lot of code removed for brevity):
Component Template:
(Goes trough each category and lists them. If the current one is active, it says so.)
<script type="text/x-handlebars" id="components/category-list">
{{#each categories}}
{{#if this.active}} Active -> {{/if}}
{{this.name}}
<br />
{{/each}}
</script>
Component Object:
(Goes through the models via the CategoriesController, marks one category as the current active category for that topic via a "current" parameter passed in from the {{category-list}} component helper)
App.CategoryListComponent = Ember.Component.extend({
tagName: '',
didInsertElement: function () {
var categories = App.CategoriesController;
var current = this.get('current').get('id');
categories.get('content').forEach(function (c) {
if (c.id === current) {
c.active = true;
}
});
this.set('categories', categories);
}.observes('current')
});
Displaying the Category List:
In the Topic view ('category' is the property of Topic that says what category the topic is in):
{{category-list current=category}}
This all works, but I have a feeling that it is not the correct way to go about solving the problem. Components strike me as something that should be very reusable, and this really only is just an encapsulated part of one view.
Second Attempt
Now I am trying to use the category list as a view. So now instead of just a Component, I have a CategoriesRoute (not hooked up in the router though), a CategoriesController, and a CagetoriesView.
When the Topic resource loads up, it sets up the categories controller with the loaded model data:
App.TopicRoute = Ember.Route.extend({
model: function (params) {
return Em.RSVP.hash({
topic: this.store.find('topic', params.topic_id),
categories: this.store.find('category')
});
},
setupController: function (controller, context) {
this._super(controller, context.topic);
this.controllerFor('categories').set('model', context.categories);
}
});
The CategoriesController is just a standard array controller:
App.CategoriesController = Ember.ArrayController.extend({});
The CategoriesView is also very simple:
App.CategoriesView = Ember.View.extend({
templateName: 'categories',
tagName: ''
});
And finally the categories template:
<script type="text/x-handlebars" id="components/category-list">
{{#each categories}}
{{this.name}}
<br />
{{/each}}
</script>
I am rendering the list with the {{render}} helper in the Topic template:
{{render "categories" }}
The Problem:
The above seems to work fine in terms of showing the list of categories. However, I can't figure out how to tell the view which category is active (the category in which the topic is in).
I looked around on the internet, and it looks like the {{render}} helper used to allow for an optional options hash (which is how I solved the problem with a Component in my first attempt), but it seems to have been removed in some recent version of Ember.
I found a StackOverflow entry that mentioned making my own {{render}} helper, but it looks like that is to dynamically change models, or something of that nature. I'd like to keep the models backing for the categories view, I just need to be able to tell it which category is active via the Topic.category property.
Or, was my first attempt the better solution? I am very new to Ember, so I'm not sure what would be the best strategy here. My instinct tells me I should use my second attempt rather than the first, but I am not positive.
Any help is very appreciated.
You are right in saying that components must be re-usable everywhere and should not be tied to any particular controller. I would use a view for this. The way I would do is, I would have a
App.CategoriesController = Em.ArrayController.extend({
itemController: 'category',
//some other stuff here
});
for categories and then have an itemController called
App.CategoryController = Em.ObjectController.extend({
setActiveState: function() {
if (this.get('parentController.current.id') === this.get('content.id')) {
this.set('active', true);
}
}
});
and in the template, you could say
{{#each category in categories}}
{{category.name}}
{{/each}}
I have this jsbin, is a relly littl modification of the todos example
http://jsbin.com/ubimes/1/
I'm trying to find the way to limit a model to the last 5 entries
return Todos.Todo.find({limit:5});
But that query returns nothing
Any idea how to limit collections ?
You can use slice instead of splice
this.get('content').slice(0, 2);
A way you could do it could be limiting your collection's content in your respective controller holding the data, like this:
App.IndexController = Ember.ArrayController.extend({
limitedContent: function() {
// in this case '2' is the limit parameter
return this.get('content').splice(0, 2);
}.property('content')
});
The .property('content') defines a binding to the controller's content and takes care that the #each helper rerenders when the content changes.
And then in your template you loop over the limitedContent rather then the content:
<script type="text/x-handlebars" data-template-name="index">
{{#each color in limitedContent}}
{{color.name}}
{{/each}}
</script>
And here a working jsbin that show the concept mentioned.
Hope it helps
I'm just starting with ember so forgive me if this question seems daft. I have an ArrayController that holds a list of people with the attribute name. I'm using the following handlebars call to create textfields for each name in the content array:
{{#each App.Controller}}
{{view Em.TextField placeholder="Name" valueBinding="name" }}
{{/each}}
this is working as expected. I first initialize my content array with several names and a textfield appears for each entry. However when I use pushObject to add a person to the content array a new textfield is not added! Using the console I can see that the people are being added to content, the view is simply not changing. Stranger still, if I add people in the following manner everything works as expected:
this.pushObject(newPerson);
var copy = this.get('content');
this.set('content',[]);
this.set('content',copy);
When I add people this way I get another textfield for the newly added person. The ember documentation says pushObject() is KVO-compliant, doesn't that mean it should update the dependencies and the view should update? Do I need to call rerender() or some other function to make pushObject() update the view or do I have to copy the array each time I want to update the view? Thanks!
So I figured out the issue. In my init function for the Controller I was simply adding elements to the content array like this
App.Controller = Em.ArrayController.create({
content: [],
init: function(){
this.insertNew();
},
insertNew: function(){
var t = App.Person.create({
name:"test"
});
this.pushObject(t);
},
});
This code did not change the view when insertNew was called (though the initialization worked). By adding this.set("content",[]) to my init function insertNew behaved as expected. Here's the working code:
App.Controller = Em.ArrayController.create({
content: [],
init: function(){
this.set("content",[]);
this.insertNew();
},
insertNew: function(){
var t = App.Person.create({
name:"test"
});
this.pushObject(t);
},
});
I had thought the first content:[] line meant I did not need to set content again later, but it seems that when initializing the array you have to set it again.
I encounter similar problems, when I use this.pushObject to add a new item, the view only updates if the item is the first object.
Later I tried with this.insertAt, it works well without any problem.
The documentation has an example of using an ArrayController with this template:
{{#each MyApp.listController}}
{{firstName}} {{lastName}}
{{/each}}
This is how the ArrayController is used:
MyApp.listController = Ember.ArrayController.create();
$.get('people.json', function(data) {
MyApp.listController.set('content', data);
});
How would this work differently than using a plain array like this instead?
MyApp.listController = [];
$.get('people.json', function(data) {
MyApp.set('listController', data);
});
If you don't need the behavior of a controller, you can use a plain array.
An ArrayController wraps an array, with some other properties added, such as the sortable mixin.
You can see it here:
https://github.com/emberjs/ember.js/blob/master/packages/ember-runtime/lib/controllers/array_controller.js
in the ember.js documentation says:
(http://docs.emberjs.com/symbols/Ember.ArrayController.html)
The advantage of using an ArrayController is that you only have to set
up your view bindings once; to change what's displayed, simply swap
out the content property on the controller.
it uses an Array in background, only helps with methods to work with the array:
Although you are binding to the controller, the behavior of this
controller is to pass through any methods or properties to the
underlying array
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.