Backbone Model Ids and on click events - backbone.js

Alright. I'm going to give in here and ask for some help. I think I'm running into multiple issues and not sure of the best approach. I'm using handlebars to create an ul of li that are my json objects to backbone models. Now that the template works I want to on click get the model to be able to add to another collection.
My first problem. I thought backbone defined a model id by default? If i set default to "" or null every model's id is "" or null. The json objects have an id I could assign to the backbone id but if I do that in the defaults id: jsonID, the jsonID is undefined. If I do object.toJSON() in the console there is no backbone created id. So I don't have a value to use in the handlebars template to assign my div id to the backbone id for that model. To then use that to get element id so that on click I could get the backbone id. Or at least I've read a lot of examples that do it that way.
My second issue, I think stems from requirejs. All the examples I see for click even use this.collection or this.model. On my View file those always return undefined, assuming this is because of requirejs. I tried this example in particular http://lostechies.com/derickbailey/2011/10/11/backbone-js-getting-the-model-for-a-clicked-element/. I'm wondering if I should just scrap using requirejs, it seems to cause more problems then help.
Here is my code so far, I deleted out my click function code because none of it was working.
Collection File:
define(['jquery', 'backbone', 'lodash', 'Models/GroceryItem'],
function($, Backbone, _, GroceryItem) {
var GroceryItems = Backbone.Collection.extend({
model: GroceryItem,
url: "data.json",
parse: function(response) {
return response.all_coupons;
}
});
var storeItems = new GroceryItems();
storeItems.fetch({
success:function(){
console.log(storeItems.toJSON());
}
});
return storeItems;
});
View File:
define(['jquery', 'backbone', 'lodash', 'handlebars', 'Collections/GroceryItems'],
function($, Backbone, _, Handlebars, storeItems) {
var GroceryItemsView = Backbone.View.extend({
template: Handlebars.compile(
'<ul class="d-row">' +
'{{#each storeItems}}' +
'<li class="lineItem" id="{{coupon_id}}">' +
'<div class="wrapper">' +
'<div class="header">{{coupon_title}}</div>' +
'<div class="column_wrapper">' +
'<div class="two-col">' +
'<div class="product_image"><img src="{{coupon_thumb}}" alt="{{coupon_description}}" height="110" width="110"></div>' +
'<div class="description">{{coupon_description}}</div>' +
'</div>' +
'</div>' +
'<div class="expiration">Valid From: {{valid_from}} to {{valid_to}}</div>' +
'</div>' +
'</li>' +
'{{/each}}' +
'</ul>'
),
events: {
"click li": "getModel"
},
getModel:function(e){
},
render: function() {
var that = this;
storeItems.fetch({
success: function(storeItems) {
var storeTemplate = that.template({storeItems: storeItems.toJSON()});
that.$el.html(storeTemplate);
return that;
}
})
return this;
}
});
return GroceryItemsView;
});
Thanks a bunch for any help. It's much appreciated. If I'm going at this completely wrong, I'm open to any suggestions. I'm just learning backbone and javascript in general so I'm grinding away as I go with a lot of googling.
Thanks!
EDITED CODE:
define(['jquery', 'backbone', 'lodash', 'Collections/GroceryItems', 'Views/GroceryItemView'],
function($, Backbone, _, storeItems, GroceryItemView) {
var GroceryItemsView = Backbone.View.extend({
tagName: 'ul',
className: 'd-row',
el: '#container',
initialize: function () {
//basically says only render when collection syncs
this.listenTo(storeItems, 'sync', this.render);
},
render: function () {
//got keep track of views for when you need close them (not important for now but you'll thank me later)
this.groceryItemsView = [];
storeItems.each(function (GroceryItem) {
//we are making a new view for each model and passing it in as an option
var itemView = new GroceryItemView({
model: GroceryItem
});
//The view creates an element but it is not attached to DOM. We will attach it to the ItemsView's $el (which IS attached to the DOM)
this.$el.append(itemView.$el);
this.groceryItemsView.push(itemView);
}, this);
}
});
var list = new GroceryItemsView();
return list;
});
define(['jquery', 'backbone', 'lodash', 'handlebars', 'Views/GroceryItemsView', 'Models/GroceryItem'],
function($, Backbone, _, Handlebars, GroceryItemsView, GroceryItem) {
var GroceryItemView = Backbone.View.extend({
template: Handlebars.compile(
'<div class="wrapper">' +
'<div class="header">{{coupon_title}}</div>' +
'<div class="column_wrapper">' +
'<div class="two-col">' +
'<div class="product_image"><img src="{{coupon_thumb}}" alt="{{coupon_description}}" height="110" width="110"></div>' +
'<div class="description">{{coupon_description}}</div>' +
'</div>' +
'</div>' +
'<div class="expiration">Valid From: {{valid_from}} to {{valid_to}}</div>' +
'</div>'
),
tagName: 'li',
className: 'lineItem',
events: {
'click': 'getModel'
},
initialize: function () {
this.render();
},
getModel: function () {
return this.model;
},
render: function () {
this.$el.html(this.template(this.model.toJSON()));
}
});
return GroceryItemView;
});

In backbone models there are actually two kinds of id's, the first is id which is meant to represent the id of your model on the server and isn't atomically assigned. The second is cid (client id) which backbone will atomically generate and assign for you.
In case your server model's id property isn't named id you can map it by setting the idAttributemodel property. For example if the id in your json model is called jsonID
var GroceryItems = Backbone.Collection.extend({
model: GroceryItem,
idAttributemodel: jsonID,
url: "data.json",
parse: function(response) {
return response.all_coupons;
}
});
Some more info on id, cid, idAttribute
I see that in your GroceryItems collection file you are both declaring your collection and instantiating it, it might make more sense in this case to just declare it and return and in your App View (or wherever you declare your collection view) instantiate it there and pass it to the view.
In order to retrieve the model id on the click event of the li, you have to options either you have a seperate view per each li which is bound to a specific model, or in your case where you are rendering all your models using the same view you can retreive it from the DOM.
For example
getModel: function (e) {
//you might want to consider using data attributes instead
var modelId = $(e.currentTarget).attr('id');
var model = this.storeItems.get(modelId);
},
In general regarding using require.js I think that while there is a bit of a learning curve in the long run it is worth it. One thing you might want to consider doing is keeping one file per view/model/collection.

The easiest way to get to a model of something you clicked is surprisingly simple.
I STRONGLY recommend NOT relying on IDs. It's very bad practice. The whole point of using Models is to stop worrying about IDs :P
Creating a Backbone View is not as expensive as some people say. It's actually quite efficient as long as you clean up properly. Break up every logical unit of DOM into it's own View. ESPECIALLY collections of Views
Require is AWESOME. Don't give up on it. Once you figure it out you'll never want to go back. Just think of it as saving bunch of code from another file to a variable defined up top
Don't use success option. Only listen to the sync event. Makes code cleaner, prevents loooooots of weird issues later on.
I haven't tested this code but the logic works (have done it many times)
//Preferrably keep this in a separate file or use require-handlebars
var itemTpl = Handlebars.compile(
'<div class="wrapper">' +
'<div class="header">{{coupon_title}}</div>' +
'<div class="column_wrapper">' +
'<div class="two-col">' +
'<div class="product_image"><img src="{{coupon_thumb}}" alt="{{coupon_description}}" height="110" width="110"></div>' +
'<div class="description">{{coupon_description}}</div>' +
'</div>' +
'</div>' +
'<div class="expiration">Valid From: {{valid_from}} to {{valid_to}}</div>' +
'</div>');
//Your Collection
var GroceryItems = Backbone.Collection.extend({
model: GroceryItem,
url: "data.json",
parse: function (response) {
return response.all_coupons;
}
});
//This Represents all your views
var ItemsView = Backbone.View.extend({
tagName: 'ul',
el: '.where-this-is-supposed-to-go',
initialize: function () {
this.collection = new GroceryItems();
//basically says only render when collection syncs
this.listenTo(this.collection, 'sync', this.render);
},
render: function () {
//got keep track of views for when you need close them (not important for now but you'll thank me later)
this.itemViews = [];
this.collection.each(function (m) {
//we are making a new view for each model and passing it in as an option
var itemView = new ItemView({
model: m
});
//The view creates an element but it is not attached to DOM. We will attach it to the ItemsView's $el (which IS attached to the DOM)
this.$el.append(itemView.$el);
this.itemViews.push(itemView);
}, this);
}
});
var ItemView = Backbone.View.extend({
template: itemTpl,
tagName: 'li',
className: 'lineItem',
events: {
'click': 'getModel'
},
initialize: function () {
this.render();
},
getModel: function () {
//it's already there. No IDs
return this.model;
},
render: function () {
this.$el.html(this.template(this.model.toJSON()));
}
});

Related

A proper example of backbone views: Change attributes, CRUD, without Zombie Views

Trying to make a reasonable teaching model of Backbone that shows proper ways to take advantage of backbone's features, with a grandparent, parent, and child views, models and collections...
I am trying to change a boolean attribute on a model, that can be instantiated across multiple parent views. How do I adjust the listers to accomplish this?
The current problem is that when you click on any non-last child view, it moves that child to the end AND re-instantiates it.
Plnkr
Click 'Add a representation'
Click 'Add a beat' (you can click this more than once)
Clicking any beat view other than the last one instantiates more views of the same beat
Child :
// our beat, which contains everything Backbone relating to the 'beat'
define("beat", ["jquery", "underscore", "backbone"], function($, _, Backbone) {
var beat = {};
//The model for our beat
beat.Model = Backbone.Model.extend({
defaults: {
selected: true
},
initialize: function(boolean){
if(boolean) {
this.selected = boolean;
}
}
});
//The collection of beats for our measure
beat.Collection = Backbone.Collection.extend({
model: beat.Model,
initialize: function(){
this.add([{selected: true}])
}
});
//A view for our representation
beat.View = Backbone.View.extend({
events: {
'click .beat' : 'toggleBeatModel'
},
initialize: function(options) {
if(options.model){
this.model=options.model;
this.container = options.container;
this.idAttr = options.idAttr;
}
this.model.on('change', this.render, this);
this.render();
},
render: function(){
// set the id on the empty div that currently exists
this.$el.attr('id', this.idAttr);
//This compiles the template
this.template = _.template($('#beat-template').html());
this.$el.html(this.template());
//This appends it to the DOM
$('#'+this.container).append(this.el);
return this;
},
toggleBeatModel: function() {
this.model.set('selected', !this.model.get('selected'));
this.trigger('beat:toggle');
}
});
return beat;
});
Parent :
// our representation, which contains everything Backbone relating to the 'representation'
define("representation", ["jquery", "underscore", "backbone", "beat"], function($, _, Backbone, Beat) {
var representation = {};
//The model for our representation
representation.Model = Backbone.Model.extend({
initialize: function(options) {
this.idAttr = options.idAttr;
this.type = options.type;
this.beatsCollection = options.beatsCollection;
//Not sure why we have to directly access the numOfBeats by .attributes, but w/e
}
});
//The collection for our representations
representation.Collection = Backbone.Collection.extend({
model: representation.Model,
initialize: function(){
}
});
//A view for our representation
representation.View = Backbone.View.extend({
events: {
'click .remove-representation' : 'removeRepresentation',
'click .toggle-representation' : 'toggleRepType',
'click .add-beat' : 'addBeat',
'click .remove-beat' : 'removeBeat'
},
initialize: function(options) {
if(options.model){this.model=options.model;}
// Dont use change per http://stackoverflow.com/questions/24811524/listen-to-a-collection-add-change-as-a-model-attribute-of-a-view#24811700
this.listenTo(this.model.beatsCollection, 'add remove reset', this.render);
this.listenTo(this.model, 'change', this.render);
},
render: function(){
// this.$el is a shortcut provided by Backbone to get the jQuery selector HTML object of this.el
// so this.$el === $(this.el)
// set the id on the empty div that currently exists
this.$el.attr('id', this.idAttr);
//This compiles the template
this.template = _.template($('#representation-template').html());
this.$el.html(this.template());
//This appends it to the DOM
$('#measure-rep-container').append(this.el);
_.each(this.model.beatsCollection.models, function(beat, index){
var beatView = new Beat.View({container:'beat-container-'+this.model.idAttr, model:beat, idAttr:this.model.idAttr+'-'+index });
}, this);
return this;
},
removeRepresentation: function() {
console.log("Removing " + this.idAttr);
this.model.destroy();
this.remove();
},
//remove: function() {
// this.$el.remove();
//},
toggleRepType: function() {
console.log('Toggling ' + this.idAttr + ' type from ' + this.model.get('type'));
this.model.set('type', (this.model.get('type') == 'line' ? 'circle' : 'line'));
console.log('Toggled ' + this.idAttr + ' type to ' + this.model.get('type'));
this.trigger('rep:toggle');
},
addBeat: function() {
this.trigger('rep:addbeat');
},
removeBeat: function() {
this.trigger('rep:removebeat');
}
});
return representation;
});
This answer should be working properly for all views, being able to create, or delete views without effecting non related views, and change attributes and have related views auto update. Again, this is to use as a teaching example to show how to properly set up a backbone app without the zombie views...
Problem
The reason you are seeing duplicate views created lies in the render() function for the Beat's view:
render: function(){
// set the id on the empty div that currently exists
this.$el.attr('id', this.idAttr);
//This compiles the template
this.template = _.template($('#beat-template').html());
this.$el.html(this.template());
//This appends it to the DOM
$('#'+this.container).append(this.el);
return this;
}
This function is called when:
when the model associated with the view changes
the beat view is first initialized
The first call is the one causing the problems. initialize() uses an event listener to watch for changes to the model to re-render it when necessary:
initialize: function(options) {
...
this.model.on('change', this.render, this); // case #1 above
this.render(); // case #2 above
...
},
Normally, this is fine, except that render() includes code to push the view into the DOM. That means that every time the model associated with the view changes state, the view not only re-renders, but is duplicated in the DOM.
This seems to cause a whole slew of problems in terms of event listeners being bound incorrectly. The reason, as far as I know, that this phenomenon isn't caused when there is just one beat present is because the representation itself also re-renders and removes the old zombie view. I don't entirely understand this behavior, but it definitely has something to do with the way the representation watches it's beatCollection.
Solution
The fix is quite simple: change where the view appends itself to the DOM. This line in render():
$('#'+this.container).append(this.el);
should be moved to initialize, like so:
initialize: function(options) {
if(options.model){
this.model=options.model;
this.container = options.container;
this.idAttr = options.idAttr;
}
this.model.on('change', this.render, this);
this.render();
$('#'+this.container).append(this.el); // add to the DOM after rendering/updating template
},
Plnkr demo with solution applied

Backbone 1.0 js events still attached after .html([content])

The problem I am having is click events keep piling up (still attached after changing the view). I have fixed the problem by only having one instance of the view (shown below). I thought backbone got rid of events when the markup is changed. I haven't had this problem with other views.
BROKEN CODE: Click events keep piling up on loadPlayerCard as more views are created.
//Player Thumb View
PgaPlayersApp.PlayerThumbView = Backbone.View.extend({
events: {
'click': 'loadPlayerCard'
},
tagName: 'li',
template: _.template( $('#player_thumb').html()),
render: function()
{
this.$el.html(this.template(this.model.toJSON()));
return this;
},
loadPlayerCard: function()
{
new PlayerCardView({model: this.model}).render();
return false;
}
});
//Router
var Router = Backbone.Router.extend({
routes:{
'': 'loadPlayers'
},
loadPlayers: function()
{
PgaPlayersApp.Players.fetch({reset: true, success: function()
{
//When players is first fetched, we want to render the first player into the card area
new PlayerCardView({model: PgaPlayersApp.Players.first()}).render();
}});
}
});
PgaPlayersApp.Router = new Router();
Backbone.history.start();
FIXED CODE: Code that fixes the problem:
PgaPlayersApp.CurrentPlayerCard = new PlayerCardView();
//Player Thumb View
PgaPlayersApp.PlayerThumbView = Backbone.View.extend({
events: {
'click': 'loadPlayerCard'
},
tagName: 'li',
template: _.template( $('#player_thumb').html()),
render: function()
{
this.$el.html(this.template(this.model.toJSON()));
return this;
},
loadPlayerCard: function()
{
PgaPlayersApp.CurrentPlayerCard.model = this.model;
PgaPlayersApp.CurrentPlayerCard.render();
return false;
}
});
//Router
var Router = Backbone.Router.extend({
routes:{
'': 'loadPlayers'
},
loadPlayers: function()
{
PgaPlayersApp.Players.fetch({reset: true, success: function()
{
//When players is first fetched, we want to render the first player into the card area
PgaPlayersApp.CurrentPlayerCard.model = PgaPlayersApp.Players.first();
PgaPlayersApp.CurrentPlayerCard.render();
}});
}
});
PgaPlayersApp.Router = new Router();
Backbone.history.start();
PlayerCardView (For reference):
var PlayerCardView = PgaPlayersApp.PlayerCardView = Backbone.View.extend({
events: {
'click': 'flipCard'
},
el: '#pga_player_card',
template: _.template( $('#player_card').html()),
render: function()
{
this.$el.html(this.template(this.model.toJSON()));
return this;
},
flipCard: function()
{
this.$("#player_card_container").toggleClass('flip');
}
});
In your router you keep creating new PlayerCardViews:
new PlayerCardView({model: PgaPlayersApp.Players.first()}).render();
All of those views share exactly the same el:
el: '#pga_player_card'
So you keep creating new PlayerCardViews and each one binds to #pga_player_card.
Every time you do that, you bind a brand new view to exactly the same DOM element and each of those views will call delegateEvents to bind the event handlers. Note that delegateEvents binds to el and that jQuery's html method:
removes other constructs such as data and event handlers from child elements before replacing those elements with the new content.
So html does nothing to el but it will remove event handlers from child elements. Consider this simple example with <div id="d"></div>:
$('#d').on('click', function() {
console.log('Before .html');
});
$('#d').html('<p>Where is pancakes house?</p>');
$('#d').on('click', function() {
console.log('After .html');
});
If you then click on #d, you'll see both the before and after messages in the console.
Demo: http://jsfiddle.net/ambiguous/ftJtS/
That simple example is, more or less, equivalent to what you're doing.
You'll have a better time if you:
Put the view inside #pga_player_card and let the router do $('#pga_player_card').append(view.render().el).
Keep track of the view that's already there and view.remove() it before adding the new one.
Avoid trying to reuse DOM elements for multiple view instances and avoid trying to reuse views, neither is worth the hassle.

backbone render not rendering select tags

I've got a simple div into which I'd like backbone to render a select box and options from my server.
The options seem to render just fine, but the select box does not. I'm sure it's a simple tweak, but can't seem to find it.
I created a simplified fiddle for it: http://jsfiddle.net/thunderrabbit/BNZY3/
The HTML
<div id="where_fields"></div>
The script I'm using uses fetch() to get the data. The Fiddle above hardcodes the data, but the issue is the same.
(function($){
var Field = Backbone.Model.extend();
var UnitFields = Backbone.Collection.extend({
url: '/<?php echo CONFIG_ADMIN_DIR; ?>/api/fieldnames/units',
model: Field
});
var BuildingFields = Backbone.Collection.extend({
url: '/<?php echo CONFIG_ADMIN_DIR; ?>/api/fieldnames/buildings',
model: Field
});
var FieldView = Backbone.View.extend({
tagName: "option",
initialize: function(){
_.bindAll(this, 'render');
},
events: {
"click":"clicked"
},
clicked: function(e) {
var data_type = this.model.get("DATA_TYPE");
if(data_type == "varchar") {
console.log("it's a varchar");
}
if(data_type == "int") {
console.log("it's an int");
}
},
render: function(){
$(this.el).attr('value', this.model.get('COLUMN_NAME')).html(this.model.get('display_name'));
return this;
}
});
var FieldsView = Backbone.View.extend({
tagName: "select",
el: $('#where_fields'),
initialize: function(){
_.bindAll(this, 'render', 'renderItem');
this.collection.bind('reset', this.render);
},
renderItem: function(model) {
console.log('rendr item');
var fieldView = new FieldView({model:model});
fieldView.render();
$(this.el).append(fieldView.el);
},
render: function(){
console.log('rendr');
this.collection.each(this.renderItem);
return this;
}
});
var units_fields = new UnitFields();
var buildings_fields = new BuildingFields();
var unitsView = new FieldsView({collection: units_fields});
var buildingsView = new FieldsView({collection: buildings_fields});
units_fields.fetch();
buildings_fields.fetch();
})(jQuery);
Why is my backbone script not rendering the select tags?
You have both tagName and el attributes in your FieldsView class. You don't need both. Use tagName if you want to render a view detached from the DOM and then backbone will use that tag instead of the default of div. However, in your render(), you don't ever actually get a select tag involved. $(this.el) is your #where_fields div and you just append fieldView.el, which is an option element. That's why there is no select element. Some quick tips:
use this.$el as a more efficient shorthand for $(this.el)
It's preferable to keep your view loosely coupled from the DOM, so el: $('#where_fields') is not as clean a design as rendering an element detached from the DOM and letting other code decide where exactly in the existing DOM it should be attached.
So you should remove your el properly, set tagName to select if you like, then your render() method will be doing what you want with is appending options to a select tag, then move the actual code to append your view's rendered el to the #where_fields div out of the view into your router perhaps.

Assign a view template based on a variable in your data?

I'm using backbone and marionette and i'd like to render the views based on a variable in the data. this.model.template I was thinking could pull from my data (returning myTemplate and myOtherTemplate) and then I could do some manipulation in the render function but it's not working. Any suggestions?. Can the view be made aware of the model?
var graph = [{
nodeName: "1st level item",
template: "myTemplate",
nodes: [{
nodeName: "2nd level item",
template: "myOtherTemplate"
}]
}];
TreeView = Backbone.Marionette.CompositeView.extend({
tagName: "ul",
initialize: function(){
this.collection = this.model.nodes;
},
appendHtml: function(collectionView, itemView){
collectionView.$("li:first").append(itemView.el);
},
render: function(){
var that = this;
console.log('Loading template name: ' + name + ' template: ' + this.template + ' data: ' + this.model.template);
TemplateManager.get(this.template, function(template){
var html = $(template).tmpl();
that.$el.html(html);
});
return this;
}
});
How are you initializing the view?
The view generally expects that the model is a Backbone model. When accessing model attribute, you should use mymodel.get('attributeName'). The individual attributes are not available directly on the model. They're available in mymodel.attributes (for example, mymodel.attributes.template) but attributes should not be used directly except for debugging because how attributes are stored and accessed may change in the future or be changed by various plugins if you use any.
Also note that in most cases you shouldn't need to override the render method. Instead, take a look at beforeRender and onRender.

Backbone.js - Filtering a collection and resetting view

I'm using Backbone.js with jquery and rails. I have a model Player and collection Players. On init I load all Players to view with PlayersView.
I would like to add the ability to filter players by Position and re-render the PlayersView accordingly. I have given it a shot but currently it just reloads all Players and doesn't filter. Please let me know if/how I can revise! Thanks.
/* Models/Collections */
var Player = Backbone.Model.extend({
url: function() {
return this.id ? '/projects/' + this.id : '/projects';
},
initialize: function(){
},
});
var Players = Backbone.Collection.extend({
model: Player,
url: '/players.json',
sortPosition : function(position){
return _(this.filter(function(data) {
return data.get("position") == position;
}));
},
});
and
// view // players //
var PlayersView = Backbone.View.extend({
el: '#players',
events: {
'click .position': 'sort',
},
initialize: function(){
this.bind("reset", this, 'render', 'sort'); // remember: every function that uses 'this' as the current object should be in here
this.render();
},
render: function(){
$('#players').show();
players.each(function(player){
firstname = player.get('first_name');
lastname = player.get('last_name');
position = player.get('position');
team = player.get('team');
$('#players-list').append('<li><span class="player-position">' + position + '</span><span class="player-name">' + firstname + ' ' + lastname + '</span><span class="player-team">' + team + '</span></li>')
});
return this;
},
sort: function(e){
var position = $(e.currentTarget).attr('data-pos');
this.render(players.sortPosition(position));
},
});
It seems like my problem is that i'm calling 'render' again which takes all players and doesn't accept a filtered set. Let me know if i can help with more code!
Thanks much.
i have no time to fully debug your code,
since there is also a few pieces (declaration) missing.
but i already see a few things that can be improved,
1) you pass players.sortPosition(position) into the render function, but your render function does not accept any arguments.
i think it is better to do them separately,
replace your view's collection by the sorted collection, and invoke render again:
sort: function(e) {
this.collection = players.sortPosition(position);
this.render();
}
2) you have a weird construction of the eventbinding in your initialize function
i've never seen it like this:
initialize: function(){
this.bind("reset", this, 'render', 'sort'); // remember: every function that uses 'this' as the current object should be in here
this.render();
},
what exactly is your intention?, if you just want to use this within the bound function,
you can use _.bindAll(this, "function1", function2", ... , "render"); if you want to bind to a certain event like "reset" i would write: this.bind('reset', this.render, this);
I know this is post is a bit old, but you can use Backbone's comparator to sort the collection.
var Players = Backbone.Collection.extend({
model: Player,
url: '/players.json',
initialize : function(){
_.bindAll(this);
this.fetch();
},
comparator: function(player){
return player.get('position')
}
});
This way, whenever you create an instance of this collection, the models in the collection are sorted by position automatically.

Resources