modify collection result in backbone - backbone.js

Now I have a returned collection data like:
[{'category':1,
'title':"Current Company",
'people':{"count":2,
"keyword":"",
"executiveInfos":[{"name":"Darren Suomi",
"title":"Global Vice President of Sales",
"companyId":2867257,
"employmentId":10993552,
"executiveId":10152454,
"imageUrl":"https://d1tzls7byl3ryp.cloudfront.net/iv/profileImages/executiveImage/321446",
"count":0,
"executiveConnectionImageUrls":[],
"trackedByWatchlists":[],
"employmentType":0},
{"name":"Gregory Gunn",
"title":"Vice President of Business Development",
"companyId":2867257,
"employmentId":9240823,
"executiveId":9049103,
"imageUrl":"https://d1tzls7byl3ryp.cloudfront.net/iv/profileImages/executiveImage/292479",
"count":0,
"executiveConnectionImageUrls":[],
"trackedByWatchlists":[],
"employmentType":0}]***}
},
{'category': 2,
'title':"Former Company",
'people':{"count":0,
"finance":0,
"otherFunction":0,
"keyword":"",
"executiveInfos":[]}
},
{'category': 4,
'title':"Family Tree Company",
'people':{"count":0,
"keyword":"",
"executiveInfos":[]}
}
]
In the data, it has 3 categories, in each category it has an attribute named people, now I
want to list the executiveInfos of people in each category, my listview is:
var PeopleListView = Backbone.View.extend({
el: $('.bucketContent'),
initialize: function(){
this.listenTo(this.collection, 'reset', this.render);
}
render: function() {
this.collection.each( function( $model) {
var itemview = new PeopleItemView({model : $model});
this.$el.prepend(itemview.render().el);
}, this);
return this;
}
});
what should I do? thank you.

Without knowing what PeopleItemView does, it is difficult to give you a proper answer. However, assuming that you are implementing this view correctly, you would need to ensure that you are properly passing over all of the model attributes to your template.
For example, assuming that the PeopleItemView view looks something like this:
var PeopleItemView = Backbone.View.extend({
this.template = JST["the_template_name"];
render: function() {
this.$el.html(this.template(this.model.attributes));
return this;
}
});
You could then iterate through executiveInfos through the template itself. In Handlebars this would look like:
{{#each people.executiveInfos}}
<span>{{name}}</span>
<span>{{title}}</span>
<span>{{companyId}}</span>
<span>{{employmentId}}</span>
<span>{{executiveId}}</span>
...
{{/each}}

Related

BackboneJS: View renders fine, but refreshes with undefined collection

I am messing around with Backbone some weeks now and made some simple applications based on tutorials. Now I started from scratch again and tried to use the nice features Backbone offers as I am supposed to.
My view gets in the way though. When the page loads, it renders fine and creates its nested views by iterating the collection.
When I call render() again to refresh the whole list of just a single entry, all of the views attributes seem to be undefined.
The model of a single entry:
Entry = Backbone.Model.extend({
});
A list of entries: (json.html is placeholder for dataside)
EntryCollection = Backbone.Collection.extend({
model: Entry,
url: 'json.html'
});
var entries = new EntryCollection();
View for a single entry, which fills the Underscore template and should re-render itself, when the model changes.
EntryView = Backbone.View.extend({
template: _.template($('#entry-template').html()),
initialize: function(){
this.model.on('change', this.render);
},
render: function(){
this.$el.html(this.template(this.model.toJSON()));
return this;
}
});
View for the whole list of entries which renders a EntryView for each item in the collection and should re-render itself, if a new item is added. The button is there for testing purposes.
EntryListView = Backbone.View.extend({
tagName: 'div',
collection: entries,
events: {
'click button': 'addEntry'
},
initialize: function(){
this.collection.on('add',this.render);
},
render: function(){
this.$el.append('<button>New</button>'); //to test what happens when a new item is added
var els = [];
this.collection.each(function(item){
els.push(new EntryView({model:item}).render().el);
});
this.$el.append(els);
$('#entries').html(this.el);
return this;
},
addEntry: function(){
entries.add(new Entry({
title: "New entry",
text: "This entry was inserted after the view was rendered"
}));
}
});
Now, if I fetch the collection from the server, the views render fine:
entries.fetch({
success: function(model,response){
new EntryListView().render();
}
});
As soon as I click the button to add an item to the collection, the event handler on EntryListView catches the 'add' event and calls render(). But if I set a breakpoint in the render function, I can see that all attributes seem to be "undefined". There's no el, there's no collection...
Where am I going wrong?
Thanks for your assistance,
Robert
As is, EntryListView.render is not bound to a specific context, which means that the scope (this) is set by the caller : when you click on your button, this is set to your collection.
You have multiple options to solve your problem:
specify the correct context as third argument when applying on
initialize: function(){
this.collection.on('add', this.render, this);
},
bind your render function to your view with _.bindAll:
initialize: function(){
_.bindAll(this, 'render');
this.collection.on('add', this.render);
},
use listenTo to give your function the correct context when called
initialize: function(){
this.listenTo(this.collection, 'add', this.render);
},
You usually would do 2 or/and 3, _.bindAll giving you a guaranteed context, listenTo having added benefits when you destroy your views
initialize: function(){
_.bindAll(this, 'render');
this.listenTo(this.collection, 'add', this.render);
},
And if I may:
don't create your main view in a fetch callback, keep it referenced somewhere so you can manipulate it at a later time
don't declare collections/models on the prototype of your views, pass them as arguments
don't hardwire your DOM elements in your views, pass them as arguments
Something like
var EntryListView = Backbone.View.extend({
events: {
'click button': 'addEntry'
},
initialize: function(){
_.bindAll(this, 'render');
this.listenTo(this.collection, 'reset', this.render);
this.listenTo(this.collection, 'add', this.render);
},
render: function(){
var els = [];
this.collection.each(function(item){
els.push(new EntryView({model:item}).render().el);
});
this.$el.empty();
this.$el.append(els);
this.$el.append('<button>New</button>');
return this;
},
addEntry: function(){
entries.add(new Entry({
title: "New entry",
text: "This entry was inserted after the view was rendered"
}));
}
});
var view = new EntryListView({
collection: entries,
el: '#entries'
});
view.render();
entries.fetch({reset: true});
And a demo http://jsbin.com/opodib/1/edit

Backbone nested views - child view attributes incorrectly set in the template

I'm trying to create a Backbone Model (lets call it Library) which contains a Collection of other Models (lets call them Books). I am providing a view - LibraryView, which creates an HTML of a book case with a set of books represented by HTML generated by BookView. Also, I am using Handlebars.js as my templating system.
The issue I am experiencing is that my BookView returns weird html on this.el element, before I even pass it through render() function.
LibraryModel Model
var LibraryModel = Backbone.Model.extend({
initialize: function() {
var books = new BookCollection();
_.each(book_data.books, function(value, index) {
books.add(new Book());
});
this.books = books;
}
});
LibraryView View:
var LibraryView = Backbone.View.extend({
el: "#library",
render: function() {
var t = this;
this.model.books.each(function(book, index) {
//create new view for each Book model in the books collection
var view = new BookView(book);
//append HTML produced by the BookView into LibraryView el element
t.$el.append(view.render().$el);
});
return this;
},
initialize: function() {
//snip
}
});
BookView View:
var BookView = Backbone.View.extend({
render: function() {
var viewmodel = this.model;
var source = $("#book-template").html();
var template = Handlebars.compile(source);
var html = template(viewmodel.toJSON());
console.log(html); //prints <div test="wtf" anotherTest="123"><b>wtf</b> 123</div>
this.$el.html(html);
return this;
},
initialize: function(book) {
console.log(this.el.outerHTML); //prints <div test="wtf" anotherTest="123"></div>
this.model = book;
this.listenTo(this.model, "change", this.render);
}
});
Template I am providing is: <b>{{test}}</b> {{anotherTest}}
BookModel Model
var BookModel = Backbone.Model.extend({
defaults: {
test: "wtf",
anotherTest: 123
},
initialize: function() {
//snip
}
});
Basically, the issue I am exeperiencing is that my BookView produces weird HTML where each of my model attributes is attached to the Backbone-generated div, like this:
<div test="wtf" anotherTest="123">
<b>wtf</b> 123
</div>
I am not setting any of the attributes anywhere else in the code - both values are only coming from the defaults.
Also, I confirmed this is not something that Handlebars is doing, as Model-attributes are inserted as HTML-atributes into Backbone generated div of the BookView model (note, I am not providing tagName or el manually, I want Backbone to create a div for me).
So here is where I am stuck. I have a perfectly working list of HTML generated by BookView for each of my models in the list, but for some reason Backbone-generated div wrapper contains each of Model-attributes in its HTML-attributes, like so:
<div id="#library">
<div test="wtf" anotherTest="123"><b>wtf</b> 123</div>
<div test="wtf" anotherTest="123"><b>wtf</b> 123</div>
<div test="wtf" anotherTest="123"><b>wtf</b> 123</div>
</div>
I am really pulling my hair out over this and I have suspicion it has something to do with the fact I am trying to use View-in-a-View.
Have you encountered similar problems before? Have you got good examples of Backbone application where MasterView renders collection of ChildViews?
You are doing a weird thing when creating new BookViews. That view is expecting a BookModel to come in its initialization. However, the initialize() method always expects an object which is called attributes by convention. You should modify your BookView so it matches this:
var BookView = Backbone.View.extend({
render: function() {
// keep the same code
},
initialize: function(attributes) {
this.listenTo(this.model, "change", this.render);
}
});
and in your LibraryView you should use:
var view = new BookView({ model : book });
instead of the previous:
var view = new BookView(film); // which is a typo, and should pass book and not film
So now you get your expected result.
<div id="library">
<div><b>wtf</b> 123</div>
<div><b>wtf</b> 123</div>
<div><b>wtf</b> 123</div>
</div>
Keep in mind that initialize(attributes) will automatically call set with that mapping, so you don't have to call set(attr, value) yourself. With that in mind, we can understand why that view is having two attributes which are in fact your two model's attributes (they are being set() on initialization).

backbone data view not showing

I have been having a few issue with backbone so decided to do a very simple tutorial.
After getting this working I tried to simplify it but now cannot get it working.
I think the problem is around returning the view to the screen..
here is the code
var Theater = {
Models: {},
Collections: {},
Views: {},
Templates:{}
}
Theater.Models.Movie = Backbone.Model.extend({})
Theater.Collections.Movies = Backbone.Collection.extend({
model: Theater.Models.Movie,
url: "scripts/data/movies.json",
initialize: function(){
console.log("Movies initialize")
}
});
Theater.Templates.movies = _.template($("#tmplt-Movie").html())
Theater.Views.Movies = Backbone.View.extend({
el: $("#mainContainer"),
template: Theater.Templates.movies,
//collection: new Theater.Collections.Movies(), //Not needed
initialize: function () {
_.bindAll(this, "render");
this.collection.bind("reset", this.render, this);
},
render: function () {
$(this.el).append(this.template(this.collection.toJSON())) ;
}
})
Theater.Router = Backbone.Router.extend({
routes: {
"": "defaultRoute"
},
defaultRoute: function () {
Theater.movies = new Theater.Collections.Movies()
new Theater.Views.Movies({ collection: Theater.movies });
Theater.movies.fetch();
}
})
var appRouter = new Theater.Router();
Backbone.history.start();
and here is the very basic html
<div id="mainContainer"></div>
<script type="text/template" id="tmplt-Movie">
<div><%=name %> </div>
</script>
thanks
this.collection.toJSON()) converts collection into a json, so trying to access name on it in the template won't give you anything.
You can write your render method like this:
render : function() {
var _view = this;
this.collection.each(function(model) {
$(_view.el).append(_view.template(model.toJSON())); // assuming model has 'name' attribute which is accessed in the template code
});
}
This should work.
You have an incorrect template
template: Theater.Templates.movies,
In the render function use
var template = _.template( $("#tmplt-Movie").html(), this.collection.toJSON() );
this.$el.html( template );
Try that. If it fails. Try some console to log to check that fetch is being called, the collection is being populated and that render is being called. If render is being called then it just a matter of correcting a small mistake that will probably be related to dom selection.
It seems that you want to provide a collection to the template, and that the template should loop through the collection and present the values. You can provide a collection to a template, but that’s probably not the best way.
The primary problem seems that you are using a colleciton where you should be using an model. In the render function you are passing a collection to the template. The template should take Models Json.
This is where sub views can be used. So you would want a primary view that takes a collection and that primary view will call a subview that will accept a model.
I did provide an example on jsFiddle.net. It’s somewhat of an hack. Instead of passing a colleciton into the template, I passed an individual item from the collection. This will only render 1 model. Since Routing can be confusing, I went ahead and removed it.
Example on jsFiddle.net. I sometime have problems with IE and jsFiddle.net. I recommend using the Chrome Browser.
this.$el.append(this.template(this.collection.at(0).toJSON()));
Just this month I did started creating more simple tutorials on Backbone.js. This list of tutorial is located at the bottom of this page:
More Simple Backbone.js Examples
Hopefully soon, I will have the time create a simple tutorial on rendering colletion.
Here's the complete code
<div id="mainContainer"></div>
var Theater = {
Models: {},
Collections: {},
Views: {},
Templates: {}
};
Theater.Models.Movie = Backbone.Model.extend({});
Theater.Collections.Movies = Backbone.Collection.extend({
model: Theater.Models.Movie,
//url: "scripts/data/movies.json",
initialize: function() {
console.log("Movies initialize")
}
});
Theater.Templates.movies = _.template($("#tmplt-Movie").html());
Theater.Views.Movies = Backbone.View.extend({
el: $("#mainContainer"),
template: Theater.Templates.movies,
//collection: new Theater.Collections.Movies(), //Not needed
initialize: function() {
_.bindAll(this, "render");
this.collection.bind("reset", this.render, this);
},
render: function() {
this.$el.append(this.template(this.collection.at(0).toJSON()));
}
});
var movies = new Theater.Collections.Movies();
var movieView = new Theater.Views.Movies({ collection: movies });
var myMovies =
[{
"Id": "BVwi1",
"Name": "Bag It",
"AverageRating": 4.6,
"ReleaseYear": 2010,
"Url": "http://www.netflix.com/Movie/Bag_It/70153545",
"Rating": "NR"
},
{
"Id": "BW1Ss",
"Name": "Lost Boy: The Next Chapter",
"AverageRating": 4.6,
"ReleaseYear": 2009,
"Url": "http://www.netflix.com/Movie/Lost_Boy_The_Next_Chapter/70171826",
"Rating": "NR"
}];
movies.reset(myMovies);
I hope this help.

How do I check to see if data exists in a particular field of my model in backbone?

I have a model.
I have notes that I add to that model.
No problems there.
When I instantiate the view, I want to see if the data.objects has content in the note.
Where do I put the if statement? In the view render? And how do I test for it.
js and backbone noob here, so please forgive me missing the basics.
Lemme know and many thanks.
Any references to tutorials are welcome.
UPDATE: Here's the View for my model
var BlasterView = Backbone.View.extend({
tagName: 'li',
className: 'blaster',
events: {
'click .td-blaster a': 'done'
},
initialize: function() {
_.bindAll(this, 'render');
},
render: function(){
this.$el.html(ich.blasterTemplate(this.model.toJSON()));
return this;
},
done: function(){
this.model.toggle();
this.$el.animate({
backgroundColor:'#faeca9'
}, 600 ).delay(600).fadeOut('fast');
return false;
}
});
render: function(){
if( this.model.get('particularField') ){
console.log('Particular Field has a value');
}else{
console.log('Particular Field does NOT have a value');
}
this.$el.html(ich.blasterTemplate(this.model.toJSON()));
return this;
},
If you mean that also the field is an instance of an object, just do this, check that particularField is not empty and also check that the nested property is set:
if( this.model.get('particularField') &&
this.model.get('particularField').someChildAttr){
UPDATE:
"Backbone now supports the has property" (via: #TyroneMichael). So you can just use:
this.model.has('particularField')

Creating a backbone view for a collection

How can I bind a backbone view to a collection rather than a model? Do I need to wrap the collection in a model?
e.g.
If I have a backbone model Client and a collection of these called Clients
Client = Backbone.Model.extend({
defaults: {
Name: ''
}
});
Clients = Backbone.Collection.extend({
model: Client,
url: 'Clients'
});
and a view
var ClientListView = Backbone.View.extend({
template: _.template($("#clients-template").html()),
el: $('#clientlist'),
initialize: function() {
_.bindAll(this, 'render');
this.collection = new Clients();
},
render: function( event ){
$(this.el).html(this.template({ this.collection.toJSON()));
return this;
}
});
then I can't access each client element in the underscore template. However if I wrap the collection like this
$(this.el).html(this.template({ clients: this.collection.toJSON() }));
then I can. Is this the correct way to go about this? I would expect this to be a common scenario but I can't find any examples on it, am I going about it the wrong way?
Yes, you need to pass the wrapped collection.
Addy Osmani is using similar approach in his Backbone Fundamentals examples - see for example this view and corresponding template:
In the view:
$el.html( compiled_template( { results: collection.models } ) );
In the template:
<% _.each( results, function( item, i ){ %>
...
<% }); %>
Another alternative is to have a view that will create separate view for each model in the collection. Here is an example from An Intro to Backbone.js: Part 3 – Binding a Collection to a View:
var DonutCollectionView = Backbone.View.extend({
initialize : function() {
this._donutViews = [];
this.collection.each(function(donut) {
that._donutViews.push(new UpdatingDonutView({
model : donut,
tagName : 'li'
}));
});
},
render : function() {
var that = this;
$(this.el).empty();
_(this._donutViews).each(function(dv) {
$(that.el).append(dv.render().el);
});
}
});
You might want to take a look at backbone collectionView.

Resources