backbone data view not showing - backbone.js

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.

Related

modify collection result in backbone

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}}

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 localstorage A "url" property or function must be specified

I am creating a small application based on a backbone example with the backbone-localstorage plugin.
When saving the data for a new model I always get the error "A "url" property or function must be specified"
After reading through several simular topics I'm still not able to find the cause for this.
Model:
directory.models.EmployeeCollection = Backbone.Collection.extend({
localStorage: new Backbone.LocalStorage("EmployeeCollection"),
model: directory.models.Employee,
store: directory.utils.store,
findByName: function(key) {
this.reset(this.store.findByName(key));
}
});
The view:
directory.views.newEmployeeView = Backbone.View.extend({
tagName: "div",
initialize: function() {
this.template = _.template(directory.utils.templateLoader.get('new-employee'));
},
events: {
"click .save": "saveEmployee"
},
render: function(eventName) {
$(this.el).html(this.template(this.model.toJSON()));
return this;
},
saveEmployee: function(event){
this.model.set({
firstName:$('#newFirstName').val(),
lastName:$('#newLastName').val(),
title:$('#newTitle').val(),
city:$('#newCity').val(),
officePhone:$('#newOfficePhone').val(),
cellPhone:$('#newCellPhone').val(),
email:$('#newEmail').val()
});
this.model.save();
window.history.back();
return false;
}
});
I think you need the new model to be a member of your collection before you attempt to persist it. Try creating a new instance of your collection and passing it to the view (probably in your router) like this:
new newEmployeeView({ collection: new EmployeeCollection() });
In your view you can use Backbone's create convenience method (see docs) to add a new instance of the model to the collection and persist it:
this.collection.create({
firstName:$('#newFirstName').val(),
lastName:$('#newLastName').val(),
title:$('#newTitle').val(),
city:$('#newCity').val(),
officePhone:$('#newOfficePhone').val(),
cellPhone:$('#newCellPhone').val(),
email:$('#newEmail').val()
});

Backbone boilerplate: "this.model is undefined"

I'm a backbone newbie, so I'm sort of fumbling on getting an app set up. I'm using the backbone-boilerplate (https://github.com/tbranyen/backbone-boilerplate) and github-viewer (https://github.com/tbranyen/github-viewer) as a reference, though when running I seem to be getting a "this.model is undefined".
Here is my current router.js:
define([
// Application.
"app",
//Modules
"modules/homepage"
],
function (app, Homepage) {
"use strict";
// Defining the application router, you can attach sub routers here.
var Router = Backbone.Router.extend({
initialize: function(){
var collections = {
homepage: new Homepage.Collection()
};
_.extend(this, collections);
app.useLayout("main-frame").setViews({
".homepage": new Homepage.Views.Index(collections)
}).render();
},
routes:{
"":"index"
},
index: function () {
this.reset();
this.homepage.fetch();
},
// Shortcut for building a url.
go: function() {
return this.navigate(_.toArray(arguments).join("/"), true);
},
reset: function() {
// Reset collections to initial state.
if (this.homepage.length) {
this.homepage.reset();
}
// Reset active model.
app.active = false;
}
});
return Router;
}
);
And my homepage.js module:
define([
"app"
],
function(app){
"use strict";
var Homepage = app.module();
Homepage.Model = Backbone.Model.extend({
defaults: function(){
return {
homepage: {}
};
}
});
Homepage.Collection = Backbone.Collection.extend({
model: Homepage.Model,
cache: true,
url: '/app/json/test.json',
initialize: function(models, options){
if (options) {
this.homepage = options.homepage;
}
}
});
Homepage.Views.Index = Backbone.View.extend({
template: "homepage",
el: '#mainContent',
render: function(){
var tmpl = _.template(this.template);
$(this.el).html(tmpl(this.model.toJSON()));
return this;
},
initialize: function(){
this.listenTo(this.options.homepage, {
"reset": function(){
this.render();
},
"fetch": function() {
$(this.el).html("Loading...");
}
});
}
});
return Homepage;
});
Thanks in advance for the help!
Update: After much googling (you should see how many tabs I have open), I think I made a little bit of headway, but still no luck. I updated my router to have the following:
app.useLayout("main-frame").setViews({
".homepage": new Homepage.Views.Index()
}).render();
I made a number of modifications to my homepage.js module to now look like this:
define([
"app",
["localStorage"]
],
function(app){
"use strict";
var Homepage = app.module();
Homepage.Model = Backbone.Model.extend({
defaults: function(){
return {
homepage: {}
};
}
});
Homepage.Collection = Backbone.Collection.extend({
//localStorage: new Backbone.LocalStorage("Homepage.Collection"),
refreshFromServer: function() {
return Backbone.ajaxSync('read', this).done( function(data){
console.log(data);
//save the data somehow?
});
},
model: Homepage.Model,
cache: true,
url: '/app/json/test.json',
initialize: function(options){
if (options) {
this.homepage = options.homepage;
}else{
//this.refreshFromServer();
}
}
});
Homepage.Views.Index = Backbone.View.extend({
template: "homepage",
el: '#mainContent',
initialize: function(){
var self = this;
this.collection = new Homepage.Collection();
this.collection.fetch().done( function(){
self.render();
});
},
render: function(){
var data = this.collection;
if (typeof(data) === "undefined") {
$(this.el).html("Loading...");
} else {
$(this.el).html(_.template(this.template, data.toJSON()));
}
return this;
}
});
return Homepage;
});
As you can see, I have localStorage code but commented out for now because I just want to get everything working first. The ultimate goal is to have an initial call that loads data from a JSON file, then continues afterwards using localStorage. The app will later submit data after the user does a number of interactions with my app.
I am getting the main view to load, though the homepage module isn't populating the #mainContent container in the main view.
I did all of the googling that I could but frustrated that it's just not sinking in for me. Thanks again for looking at this and any feedback is appreciated!
I think your class hierarchy is a bit wonky here. Your instance of Homepage.Collection is actually assigning a homepage property out of options, for instance. Then you pass an instance of Homepage.Collection into Homepage.Views.Index as the homepage option... It's a bit hard to follow.
That said, it seems to me your problem is simply that you aren't supply a model option when you construct your Homepage.Views.Index:
new Homepage.Views.Index(collections)
collections doesn't have a model property, and thus I don't see how this.model.toJSON() later on in the view can have a model to access. Basically, you seem to want Homepage.Views.Index to handle a collection of models, not just one. So you probably need a loop in your render function that goes over this.collection (and you should change your construction of the view to have a collection option instead of homepage option).
If I'm missing something here or I'm unclear it's because of this data model oddness I mentioned earlier. Feel free to clarify how you've got it reasoned out and we can try again :)
This example code you have is a little bit confusing to me, but I think the problem lies in the following two lines of code:
".homepage": new Homepage.Views.Index(collections)
$(this.el).html(tmpl(this.model.toJSON()));
It looks like you pass a collection to the view, but in the view you use this.model, hence the error "this.model is undefined", since it is indeed undefined.
If you aren't in any rush, may I suggest that you start over. It seems you are trying too much too quickly. I see that you have backbone, requirejs (or some other module loader), and the boilerplate, which is a lot to take in for someone new to backbone. Trust me, I know, because I am relatively new, too. Maybe start with some hello world stuff and slowly work your way up. Otherwise, hacking your way through bits of code from various projects can get confusing.

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