How to access a specific view in backbone.js? - javascript

I have a backbone viev which generates a list item view. In the render function it goes through the collection and generates each of the sub item views with some standard code as follows:
render: function () {
_(this.collection.models).each(function(item){
$this.appendItem(item);
}, this);
},
I would like to know how to access a specific view from the item list, say at position 0 or something. I want to be able to trigger a function from the item list view for that specific item.

Well, that depends on how the appendItem function is implemented, I guess is in there where you build the SubViews.
You can store each created SubView in an Array so the Array will offer you a way to manipulate the SubViews.
But if I can offer a piece of advice I would say that this is a code smell from the begining. Instead of trying to manipulate a concrete SubView you can manipulate the Model which is associated with the SubView and make the SubView to be listening to this change.
Then you will start to be thinking in manipulating Models instead of Views.

Related

Auto re-render in Ampersand.js

I have a main app view, which has 2 subviews. Each subview has its own collection passed to it.
One of these collections is being updated during the user journey. But the subview associated with it is not updating.
How can I make the view re-render once the collection is updated?
As I understand, you are rendering several items, each of which corresponds to one model in your collection. In this case, to take advantage of dynamic inserting/removing of corresponding items, you should use renderCollection like this in your subview:
render: function(){
this.renderWithTemplate(this);
this.renderCollection(this.collection, ItemView, this.el.querySelector('SELECTOR'));
return this;
}
Here ItemView is the view which corresponds to one model in the collection (it's the constructor, not instantiated view). From this view you have access to this.model and in this view you can define any bindings and events you might want. For more details check out official documentation.

Adding a Collection to a ul in backbonejs

I'm just learning js/backbonejs and I have a simple question, Please feel free to direct me to a duplicate.
I have a Collection, I've populated it and I can access it in the console by doing the standard:
collection1.at(1).get('name');
I can also loop through the values by doing:
for(vars i = 0; i < collection1.size(); i++)
{
console.log(collection1.at(i).get('name'));
}
I have four buttons and have views on them and functions that correctly output something to the console when i click on them. When i click on the Show all button i want to display every model in the collection along with the data it has (id,name,fame);
How would i go about doing this? I know i have to have a
<ul id = "gottaChangeThis"></ul>
How would I be able to add something like this to it:
<li><%=id%><%=name%><%=fame%></li>
Any help or redirection would be helpful, Thanks
The basic architecture could include a Backbone.View that accepts your Collection. On render, iterate through the Models in the collection, and for each one render a different Backbone.View (to render the <li>) and append it to the parent <ul> element.
As an alternative, consider using Marionette. It's a Backbone framework/extension that gives you additional objects as a means to eliminate a lot of boilerplate. In your case, you'd want a Marionette.CollectionView with a childView specified. This childView could be a Marionette.ItemView, such that when you render the CollectionView, it automagically instantiates and renders a childView for each Model in your Collection.

Count or Select Backbone View Instances

Let's say I'm trying to create a toDo application, where clicking each toDo opens an edit form for each toDoItem. I only want a maximum of one edit form open at any one time, so right now I am doing this in the edit method of the toDoItem view:
edit: function (e) {
e.preventDefault();
if ($('.editForm').length == 0) {
//create form model and view
}
}
That works, but doesn't seem very Backbone-y. Is there are way to select or count all instances of a particular view (in this case, the form-view)?
AFAIK, there are no utilities method in Backbone.View to count instances of a particular Views. Here are some ideas...
Maybe each of your TODO form is tied to a Model? In that case, you can have a model.set/get 'editing' and a collection.isAlreadyEditing() which would filter the models on this field:
(collection.filter(function(model){ return model.get("editing") }).length > 0
That would allow you to use on change:editing events throughout your views to control the logic and have convenient helpers functions in the collection to define some behavior of all those TODO as a whole. This would be one way to implement something closer to a Controller pattern in Backbone.
Another common thing in backbone is to keep an array of all the subviews when you instanciate them, so you could just do a:
_.any(subViews, function(view){return view.editing; })
Assuming that you keep a editing flag in your subviews when it gets toggled.
You can have your views listen to a toggleEdit event with the id or the model or something identifying what is being edited, sometimes the event handler can be as simple as a toggleClass("open", model==this.model)...
I am sure there are millions of other ideas. But counting jQuery selected elements is probably not very high on the list!

Backbone: reverse collection order with comparator

I'm building a Backbone app which displays a list of books, but when I add a new book, through the Edit view, he goes to the bottom of the list instead to the top. So basically I want to reverse the order of my collection with a Comparator but what I tried it's not working:
comparator: function (model) {
return -model.get('id');
}
Here is a JSFiddle with all the code: http://jsfiddle.net/swayziak/9R9zU/4/
I don't know if the problem is only related with the comparator or if I need to change something more in other part of the code.
Why not a simple prepend instead of append
this.$el.prepend(
Your comparator is looking for the model IDs:
comparator: function (model) {
return -model.get('id');
}
but none of your models have id attributes. Usually the id would come from the server so the server would supply the initial id values when bootstrapping the collection and then new ids would be assigned (by the server) when you save the model.
If you add ids to your data then things will start to make more sense.
You'll also need to adjust your fiddle to:
this.listenTo(this.collection, 'add', this.render);
instead of:
this.listenTo(this.collection, 'add', this.renderBook);
since your editing panel will kill off all the HTML and you'll need to re-render the whole collection.
Once you get past that, you'll find that your Edit link no longer works. That's because you're trying to re-use views while messing around with the content of their el's. Instead, you should:
Stop trying to re-use views, it is almost never worth the hassle.
Give each view its own unique el.
Call view.remove() to get rid of a view before putting a new one in the common container.
Then create the new view, render it, and put its el in the container.
You'll find that since all your views share a common container, you'll no longer need to bind your collection view to the collection's 'add' event, you'll be tossing and rebuilding the whole view instead.

backbone.js - collections and views

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.

Categories