Ok I have a layout like the one in this pic:
The table in the upper part of the screen is made by:
MessageListView
define(['backbone','collections/messages','views/message'], function(Backbone, MessageCollection, MessageView) {
var MessageListView = Backbone.View.extend({
el: '#messagesContainer',
initialize: function() {
this.collection = new MessageCollection();
this.collection.fetch({reset:true});
this.listenTo( this.collection, 'reset', this.render );
this.table = this.$el.find("table tbody");
this.render();
},
render: function() {
this.collection.each( function(message, index) {
this.renderMessage(message, index);
}, this);
},
renderMessage: function(message, index) {
var view = new MessageView({
model:message,
className: (index % 2 == 0) ? "even" : "odd"
});
this.table.append( view.render().el );
}
});
return MessageListView;
});
MessageView
define(['backbone','models/message'], function(Backbone, MessageCollection, MessageView) {
var MessageView = Backbone.View.extend({
template: _.template( $("#messageTemplate").html() ),
render: function() {
this.setElement( this.template(this.model.toJSON()) );
return this;
},
events:{
'click':'select'
},
select: function() {
// WHAT TO DO HERE?
}
});
return MessageView;
});
AppView
define(['backbone','views/messages'], function(Backbone, MessageList) {
var App = Backbone.View.extend({
initialize: function() {
new MessageList();
}
});
return App;
});
I will soon add a new view (maybe "PreviewView") in the lower part of the screen.
I want to make something happen inside the "PreviewView" when user clicks a row.
For example, it could be interesting to display other model's attributes (details, e.g.) inside the PreviewView.
What is the best practice?
holding a reference to PreviewView inside each MessageView ?
triggering events inside select method, and listening to them using on() inside the preview view.
using a transient "selected" attribute in my model, and make PreviewView listen to collection "change" events?
Thank you, if you need more details tell me please, I'll edit the question.
Not sure about the best practice but I found this solution trivial to implement. I created a global messaging object, bus, whatever:
window.App = {};
window.App.vent = _.extend({}, Backbone.Events);
You have to register the "triggerable" functions of PreviewView on the previously created event bus (according to your example, this should be in the PreviewView):
initialize: function () {
App.vent.on('PreviewView.show', this.show, this);
}
Now you should be able to trigger any of registered events from anywhere within your application by calling: App.vent.trigger. For example when the user click on a row you will have something similar:
App.vent.trigger('PreviewView.show');
in case if you have to send and object along with the triggered event use:
App.vent.trigger('PreviewView.show', data);
I am pretty new to Backbone and Backbone.Marionette. I succeed to create a simple page with a sort of datagrid that allows me paging (first, previous, next, last pages), quick searching (triggered at each time a key is pressed), choosing the number of items shown on page (5, 10, all, ...)
Now that I have something working, I tried to improve that and to make these features as a sort of reusable component but I do not know exactly the way to follow. I do not know how to start to refine the work already done.
For example, I want to be able to change the collection/model manage by the datagrid without rewriting everything. This is where I am not confident how to do that and it is probably due to a lack of knowledge. So your inputs and advice to go further will be really appreciated and welcomed.
// JST and HAML Assets is used for the templating pre-compilation
Backbone.Marionette.Renderer.render = function(template, data) {
if (!JST[template]) {
throw "Template '" + template + "' not found!";
}
return JST[template](data);
};
window.MyApp = new Backbone.Marionette.Application();
MyApp.addRegions({
content: ".content-box"
});
MyApp.Datagrid = (function() {
var Datagrid, ItemPerPageView, Layout, PagerView, QuickSearchView, Theme, ThemeView, Themes, ThemesView;
Datagrid = {};
Layout = Backbone.Marionette.Layout.extend({
template: "layouts/grid",
regions: {
grid: "#grid",
quickSearch: "#quickSearch",
itemPerPage: "#itemPerPage",
pager: ".pager"
}
});
Theme = Backbone.Model.extend();
Themes = Backbone.ExtendedCollection.paginatedCollection.extend({
url: "/themes",
model: Theme,
initialize: function() {
var _this = this;
MyApp.vent.on("quickSearch:term", function(term) {
_this.quickSearch(term);
});
MyApp.vent.on("itemPerPage:count", function(count) {
_this.perPage(count);
});
MyApp.vent.on("pager:previous", function() {
_this.previous();
});
MyApp.vent.on("pager:next", function() {
_this.next();
});
MyApp.vent.on("pager:first", function() {
_this.first();
});
MyApp.vent.on("pager:last", function() {
_this.last();
});
}
});
ThemeView = Backbone.Marionette.ItemView.extend({
tagName: "tr",
template: "theme",
model: Theme,
events: {
"click span": "edit",
"blur input": "save"
},
edit: function(event) {
var id, span;
id = this.model.get("id");
span = $("span", this.el).hide();
$("input", this.el).show().focus().val(span.text());
},
save: function(event) {
var id, input, span;
id = this.model.get("id");
span = $("span", this.el).show();
input = $("input", this.el).hide();
if (this.model.get("name") !== input.val()) {
this.model.set("name", input.val());
this.model.save();
}
span.text(this.model.get("name"));
}
});
ThemesView = Backbone.Marionette.CompositeView.extend({
template: "index",
model: Theme,
itemView: ThemeView,
collection: Themes,
itemViewContainer: "#themes",
serializeData: function() {
return this.data;
}
});
QuickSearchView = Backbone.Marionette.View.extend({
el: "#quickSearch",
events: {
"keyup input": "search"
},
search: function(event) {
var searchTerm;
searchTerm = this.$("input").val().trim();
MyApp.vent.trigger("quickSearch:term", searchTerm);
}
});
ItemPerPageView = Backbone.Marionette.View.extend({
el: "#itemPerPage",
events: {
"change select": "count"
},
count: function(event) {
var count;
count = this.$("select").val();
MyApp.vent.trigger("itemPerPage:count", count);
}
});
PagerView = Backbone.Marionette.View.extend({
el: ".pager",
events: {
"click #next": "next",
"click #previous": "previous",
"click #first": "first",
"click #last": "last"
},
first: function(event) {
MyApp.vent.trigger("pager:first");
},
last: function(event) {
MyApp.vent.trigger("pager:last");
},
next: function(event) {
MyApp.vent.trigger("pager:next");
},
previous: function(event) {
MyApp.vent.trigger("pager:previous");
}
});
Datagrid.initializeLayout = function() {
var collection;
Datagrid.layout = new Layout();
Datagrid.layout.on("show", function() {
MyApp.vent.trigger("layout:rendered");
});
MyApp.content.show(Datagrid.layout);
collection = new Themes();
collection.fetch();
collection.on("reset", function() {
return Datagrid.layout.grid.show(new ThemesView({
collection: collection
}));
});
};
MyApp.vent.on("layout:rendered", function() {
var itemPerPageView, pagerView, quickSearchView;
quickSearchView = new QuickSearchView();
Datagrid.layout.quickSearch.attachView(quickSearchView);
itemPerPageView = new ItemPerPageView();
Datagrid.layout.itemPerPage.attachView(itemPerPageView);
pagerView = new PagerView();
Datagrid.layout.pager.attachView(pagerView);
});
return Datagrid;
})();
MyApp.addInitializer(function() {
MyApp.Datagrid.initializeLayout();
});
$(document).ready(function() {
return MyApp.start();
});
Edit 1:
Based on the answer given and my own ideas, I wrote a first draft of a solution. I did not succeed to write a real reusable component but I have a solution that consolidate my code. Some part need to be refactored and improved. There are also some pitfals that I want to solve in a later refactoring.
To add some context, the application is written with Rails as the backend. So there is my javascript folder structure
assets
|--javascripts
|--application.js
|--admin
|--admin.js
|--admin.layout.js
|--subthemes
|--admin.subtheme.controller.js
|--admin.subtheme.view.js
|--themes
|--admin.theme.controller.js
|--admin.theme.view.js
|--templates
|--admin
|--subthemes
|--index.hamlc
|--subtheme.hamlc
|--themes
|--index.hamlc
|--theme.hamlc
|--layouts
|--grid.hamlc
First, the application.js start. The assets pipelines from Rails 3.2 will prepare the dependencies as expected:
//= require underscore
//= require backbone
//= require backbone.marionette
//= require_tree ./lib/backbone
//= require hamlcoffee
//= require i18n
//= require i18n/translations
//= require_tree ../templates/
//= require_tree ./admin
//= require_tree ./admin/theme
//= require_tree ./admin/subtheme
I18n.defaultLocale = "en";
Backbone.Marionette.Renderer.render = function(template, data) {
if (!JST[template]) {
throw "Template '" + template + "' not found!";
}
return JST[template](data);
};
$(document).ready(function() {
return MyApp.start();
});
Now, we can prepare the admin part to start:
var AdminRouter, TempView;
// Create the application for admin part
MyApp.Admin = new Backbone.Marionette.Application();
// Define a router to handle the grid collection type change
AdminRouter = Backbone.Marionette.AppRouter.extend({
initialize: function() {
var _this = this;
// Route quite generic to easily change the data in the grid
this.route(/^admin\/(.*?)$/, "changeCollection");
// Manage event to handle the navigation on client side
MyApp.Admin.vent.on("admin:navigate", function(link) {
_this.navigate(link, {
trigger: true
});
});
},
// Trigger an event to change the collection if one exist for the URL
changeCollection: function(collectionName) {
MyApp.Admin.vent.trigger("grid:collection:change", collectionName);
}
});
// Side menu that allows changing the collection in the data grid
SideMenuView = Backbone.Marionette.View.extend({
el: ".side-menu",
events: {
"click a": "handleClick"
},
// Prevent the normal behavior on the link click
handleClick: function(event) {
event.preventDefault();
MyApp.Admin.vent.trigger("admin:navigate", $(event.target).attr("href"));
}
});
// Add the initializer to the main application to prepare the admin part (grid)
MyApp.addInitializer(function() {
new SideMenuView();
new AdminRouter();
Backbone.history.start({
pushState: true
});
MyApp.Admin.start();
});
Then we can define the datagrid part:
// This the grid layout module in the admin namespace
MyApp.Admin.module("GridLayout", function(GridLayout, Admin, Backbone, Marionette, $, _) {
var ItemPageSelectorView, Layout, PagerView, QuickSearchView;
// The quick search view handle the related fields to do the quick search
QuickSearchView = Backbone.Marionette.View.extend({
el: ".gridQuickSearch",
events: {
"keyup input": "search"
},
// Get the field content and trigger an event with it
search: function(event) {
var searchTerm;
searchTerm = $(event.target).val().trim();
$("input", this.$el).val(searchTerm);
Admin.vent.trigger("grid:quickSearch:term", searchTerm);
}
});
// The item page selecto handle the choice of how many rows should be displayed per page
ItemPageSelectorView = Backbone.Marionette.View.extend({
el: ".gridItemPageSelector",
events: {
"change select": "count"
},
// Get the number of items per page that should be displayed
count: function(event) {
var count;
count = $(event.target).val();
$("select", this.$el).val(count);
Admin.vent.trigger("grid:itemPageSelector:count", count);
}
});
// The pager view manage the view components to change the page shown in the data grid
PagerView = Backbone.Marionette.View.extend({
el: ".gridPager",
events: {
"click #next": "next",
"click #previous": "previous",
"click #first": "first",
"click #last": "last",
"click #page": "page"
},
//
// The following functions triggers events to go to the right pages
//
first: function(event) {
Admin.vent.trigger("grid:pager:first");
},
previous: function(event) {
Admin.vent.trigger("grid:pager:previous");
},
page: function(event) {
Admin.vent.trigger("grid:pager:page");
},
next: function(event) {
Admin.vent.trigger("grid:pager:next");
},
last: function(event) {
Admin.vent.trigger("grid:pager:last");
}
});
// The grid layout with the regions to display the different part of the data grid
Layout = Backbone.Marionette.Layout.extend({
template: "layouts/grid",
regions: {
gridTable: "#gridTable",
gridQuickSearch: ".gridQuickSearch",
gridItemPageSelector: ".gridItemPageSelector",
gridPager: ".gridPager"
}
});
// Once the layout is rendered, the different views are attached to the right regions
Admin.vent.on("grid:layout:rendered", function() {
var itemPageSelectorView, pagerView, quickSearchView;
quickSearchView = new QuickSearchView();
Admin.gridLayout.gridQuickSearch.attachView(quickSearchView);
itemPageSelectorView = new ItemPageSelectorView();
Admin.gridLayout.gridItemPageSelector.attachView(itemPageSelectorView);
pagerView = new PagerView();
Admin.gridLayout.gridPager.attachView(pagerView);
});
// Initializer to do at the application start
GridLayout.addInitializer(function() {
Admin.addRegions({
content: ".content-box"
});
Admin.gridLayout = new Layout();
// Trigger the rendered event when the grid layout is shown
Admin.gridLayout.on("show", function() {
Admin.vent.trigger("grid:layout:rendered");
});
// Manage the collection data change
Admin.vent.on("grid:collection:change", function(collectionName) {
// Close the previous view in the grid table region
Admin.gridLayout.gridTable.close();
// Trigger an event to fetch the collection
Admin.vent.trigger("" + collectionName + ":collection:fetch");
// Show the grid layout if not already done
if (!this.shown) {
this.shown = true;
Admin.content.show(Admin.gridLayout);
}
});
});
return GridLayout;
});
We are done on the structural code. Now we can go to one of the controller. For example, the ThemeController:
MyApp.Admin.module("ThemeController", function(ThemeController, Admin, Backbone, Marionette, $, _) {
// Define the model to use in the collection
ThemeController.Theme = Backbone.Model.extend();
// Define the collection with the related url on the server. The collection extends a paginated collection that has the methods to manage the quick search and the pagination
ThemeController.Themes = Backbone.ExtendedCollection.paginatedCollection.extend({
url: "/admin/themes",
model: ThemeController.Theme,
initialize: function() {
var _this = this;
//
// The following functions handle the events for the quick search and pagination
//
Admin.vent.on("grid:quickSearch:term", function(term) {
_this.quickSearch(term);
});
Admin.vent.on("grid:itemPageSelector:count", function(count) {
_this.perPage(count);
});
Admin.vent.on("grid:pager:previous", function() {
_this.previous();
});
Admin.vent.on("grid:pager:next", function() {
_this.next();
});
Admin.vent.on("grid:pager:first", function() {
_this.first();
});
return MyApp.Admin.vent.on("grid:collection:fetched", function() {
Admin.gridLayout.gridTable.show(new Admin.ThemeView.Table({
collection: _this
}));
});
}
});
// At the application initilization, we need to be sure this controller can
// handle the event to fetch the data from the server
Admin.addInitializer(function() {
Admin.vent.on("themes:collection:fetch", function() {
ThemeController.themes = new ThemeController.Themes();
// Once the data are fetched from the server, trigger an event to display them
ThemeController.themes.fetch({
success: function() {
Admin.vent.trigger("grid:collection:fetched");
}
});
});
});
});
And finally the views for the previous controller:
MyApp.Admin.module("ThemeView", function(ThemeView, Admin, Backbone, Marionette, $, _) {
// The view to show one item in a row of the data grid
ThemeView.Item = Backbone.Marionette.ItemView.extend({
tagName: "tr",
template: "admin/themes/theme",
model: Admin.ThemeController.Theme
});
// The view to show the collection of item
ThemeView.Table = Backbone.Marionette.CompositeView.extend({
template: "admin/themes/index",
model: Admin.ThemeController.Theme,
itemView: ThemeView.Item,
collection: Admin.ThemeController.Themes,
itemViewContainer: "#themes",
// ! I was force to add this to have data in the original format that is used by my templates !
serializeData: function() {
return this.data;
}
});
});
Remark: The subtheme controller and view files contains exactly the same kind of codes. Only templates and kind of stuffs differ.
The grid layout in HAML compiled through Rails assets pipeline looks like:
.gridPager
%button#first= "<<"
%button#previous= "<"
%button#next= ">"
%button#last= ">>"
%span.gridItemPageSelector= "Item per page"
%select
%option= 5
%option= 10
%option{"value" => -1}= "All"
%span.gridQuickSearch= "Quick search:"
%input#gridSearchTerm{"type" => "text"}
#gridTable
%span.gridItemPageSelector= "Item per page"
%select
%option= 5
%option= 10
%option{"value" => -1}= "All"
%span.gridQuickSearch= "Quick search:"
%input#gridSearchTerm{"type" => "text"}
.gridPager
%button#first= "<<"
%button#previous= "<"
%button#next= ">"
%button#last= ">>"
As you can see, there is quite a lot of repetition. I wanted to have quick search and pagination on top and bottom of my grid. At the moment, the simplest way to do that is to duplicate the code. I will change that later when I will find how to do that.
The template for table that shows the themes:
%table.table.table-striped
%thead
%tr
%th= "Id"
%th= "Name"
%tbody#themes
Quite simple and nothing special to say. At this time, the headers are hardcoded !
And finally, the item view template to show a theme:
%td= this.id
%td= this.name
This template is realy simple.
I am in a situation were that is pretty well working. For example, when I click on other links to change the collection shown, the quick search fields and kind stuff like that are not reinitialized. For that, I wanted to add a sort of state management to keep trace of the collection state and when come back to an already shown collection, I want to show it as it was previously.
I am sure that my solution is not perfect and could be refactored a lot. I also probably did a lot of "newbie" mistakes. So feel free to challenge my proposition. I try to learn and improve my solution and hope it will help someone to do something like that.
Well I'm not a big expert but that's how I did it, using Marionette and Requirejs:
a) I created a generic Grid Layout wiew that is called by my approuter with some parameter like collection, cols config (I render the table head with a each cycle) and a row view:
showUsers: function(){
require(['views/GridGen','collections/user_collection'], function(Grid, UserCollection){
var Users = new UserCollection();
App.grid = new Grid({collection: Users ,
rowView: 'rowUser',
cols_config: App.tables.users});
App.page.show(App.grid);
});
},
b) in my Grid Layout I render the various pieces waiting for the onShow event:
var Grid = Backbone.Marionette.Layout.extend({
regions: {
top_controls: "#top_controls",
table_view: "#table_view",
pagination_controls: "#pagination_controls",
bottom_controls: "#bottom_controls",
},
onShow: function(){
this.renderTable(this.collection);
},
renderTable: function(collection){
collection.fetch({success:function(){
require(['views/'+self.options.rowView+'.js'],function(iView){
var vista = new View({collection: collection, itemView: iView, thead: self.options.cols_config});
App.grid.table_view.show(vista);
self.renderPagination(collection);
collection.pager();
});
}});
}
c) my generic Table View take the cols and the itemView to render as parameter like this:
var View = Backbone.Marionette.CompositeView.extend({
initialize: function(){
this.itemView = this.options.itemView;
},
serializeData: function(){
var data = Backbone.Marionette.ItemView.prototype.serializeData.apply(this, arguments);
data.thead = this.options.thead;
return data;
},
appendHtml: function(collectionView, itemView, index){
collectionView.$("tbody").append(itemView.el);
},
That's just a general idea, I don't think it's the best way to do it but I didn't find a better solution yet, hope to give you at least some hints :)
I highly recommend the backgrid component which is extensible and reusable out of the box.
Only at version 0.2.6 - but good following and its pretty slick
I am having problems including an additional model into my view which is based on a collection. I have a list of comments which is created by a parent view. Its need that I have the current user name when rendering the comments to show delete button and to highlight if its his own comment. The problem is now that I cant access in CommentListView the model session, so this.session in initialize or a call from a method like addAllCommentTo list is undefinied. What I am doing wrong here? I thought its easily possible to add another object to an view appart from the model.
CommentListView:
window.CommentListView = Backbone.View.extend({
el: $("#comments"),
initialize: function () {
this.model.bind('reset', this.addAllCommentToList, this);
this.model.bind('add', this.refresh, this);
this.model.bind('remove', this.refresh, this);
},
refresh: function(){
this.model.fetch();
},
addCommentToList : function(comment) {
console.log("comment added to dom");
//need to check why el reference is not working
$("#comments").append(new CommentView({model:comment, sessionModel: this.session}).render().el);
},
addAllCommentToList: function() {
$("#comments").empty();
this.model.each(this.addCommentToList);
}
});
Call from parent list in initialize method:
window.UserDetailView = Backbone.View.extend({
events: {
"click #newComment" : "newComment"
},
initialize: function () {
this.commentText = $("#commentText", this.el);
new CommentListView({ model: this.model.comments, session: this.model.session });
new LikeView({ model: this.model.like });
this.model.comments.fetch();
},
newComment : function() {
console.log("new comment");
this.model.comments.create(
new Comment({text: this.commentText.val()}), {wait: true}
);
this.commentText.val('');
}
});
Model:
window.UserDetail = Backbone.Model.extend({
urlRoot:'/api/details',
initialize:function () {
this.comments = new Comments();
this.comments.url = "/api/details/" + this.id + "/comments";
this.like = new Like();
this.like.url = "/api/details/" + this.id + "/likes";
this.session = new Session();
},
...
});
I see one problem, but can there be others.
You are initializing the View like this:
new CommentListView({ model: this.model.comments, session: this.model.session });
And you are expecting into your View to have a reference like this this.session.
This is not gonna happen. All the hash you send to the View constructor will be stored into this.options, from Backbone View constructor docs:
When creating a new View, the options you pass are attached to the view as this.options, for future reference.
So you can start changing this line:
$("#comments").append(new CommentView({model:comment, sessionModel: this.session}).render().el);
by this other:
$("#comments").append(new CommentView({model:comment, sessionModel: this.options.session}).render().el);
Try and tell us.
Updated
Also change this line:
this.model.each(this.addCommentToList);
by this:
this.model.each(this.addCommentToList, this);
The second argument is the context, in other words: what you want to be this in the called handler.
I'm doing my first application in backbone and i get a strange thing happening trying to attach an event.
I got this code so far:
//View for #girl, EDIT action
GirlEditView = Backbone.View.extend({
initialize: function(el, attr) {
this.variables = attr;
console.log(attr);
this.render();
},
render: function() {
var template = _.template( $("#girl_edit").html(), this.variables );
$(this.el).html( template );
$("#edit_girl").modal('show');
}
});
//View for #girl
GirlView = Backbone.View.extend({
initialize: function(el, attr) {
this.variables = attr;
this.render();
},
render: function() {
var template = _.template( $("#girl_template").html(), this.variables );
$(this.el).html( $(this.el).html() + template );
},
events: {
"click p.modify": "modify"
},
modify: function() {
//calls to modify view
new GirlEditView({el : $("#edit_girl")}, this.variables);
}
});
//One girl from the list
Girl = Backbone.Model.extend({
initialize: function() {
this.view = new GirlView({el : $("#content")}, this.attributes );
}
});
//all the girls
Girls = Backbone.Collection.extend({
model: Girl,
});
//do magic!
$(document).ready(function() {
//Underscore template modification
_.templateSettings = {
escape : /\{\[([\s\S]+?)\]\}/g,
evaluate : /\{\[([\s\S]+?)\]\}/g,
interpolate : /\{\{([\s\S]+?)\}\}/g
}
//get initial data and fill the index
var list = [];
$.getJSON('girls.json', function(data) {
list = [];
$.each(data, function(key, val) {
list.push( new Girl(val) );
});
var myGirls = new Girls(list);
console.log( myGirls.models);
});
});
As you can see.
I'm using a collection to store all the girls and the data comes from a REST api in ruby.
Each girls create a new model instance and inside i attached a view instance.
I don't know if it's a good practice but i can't think a better way to do it.
Each view makes a content with a unique id. girl-1 girl-2 and go on.
Now, the template have a edit button.
My original idea is to attack the onclick event and trigger the edit view to get rendered.
That is working as expected.
The proble so far is:
When the events triggers, all the collection (girls) fire the edit view, not the one that "owns" the rendered view.
My question is what i'm doing wrong?
Thanks a lot
All the edit-views come up because all the GirlViews are using the same el:
this.view = new GirlView({el : $("#content")}, this.attributes );
and then you render be appending more HTML:
render: function() {
var template = _.template( $("#girl_template").html(), this.variables );
$(this.el).html( $(this.el).html() + template );
}
Backbone events are bound using delegate on the view's el. So, if multiple views share the same el, you'll have multiple delegates attached to the same DOM element and your events will be a mess of infighting.
You have things a little backwards: models do not own views, views watch models and collections and respond to their events. You'll see this right in the documentation:
constructor / initialize new View([options])
[...] There are several special options that, if passed, will be attached directly to the view: model, collection, [...]
Generally, you create a collection, c, and then create the view by handing it that collection:
var v = new View({ collection: c })
or you create a model, m, and then create a view wrapped around that model:
var v = new View({ model: m })
Then the view binds to events on the collection or model so that it can update its display as the underlying data changes. The view also acts as a controller in Backbone and forwards user actions to the model or collection.
Your initialization should look more like this:
$.getJSON('girls.json', function(data) {
$.each(data, function(key, val) {
list.push(new Girl(val));
});
var myGirls = new Girls(list);
var v = new GirlsView({ collection: myGirls });
});
and then GirlsView would spin through the collection and create separate GirlViews for each model:
var _this = this;
this.collection.each(function(girl) {
var v = new GirlView({ model: girl });
_this.$el.append(v.render().el);
});
Then, GirlView would render like this:
// This could go in initialize() if you're not certain that the
// DOM will be ready when the view is created.
template: _.template($('#girl_template').html()),
render: function() {
this.$el.html(this.template(this.model.toJSON());
return this;
}
The result is that each per-model view will have its own distinct el to localize the events. This also makes adding and removing a GirlView quite easy as everything is nicely wrapped up in its own el.
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.