Backbone.js Collection not iterating - javascript

PaperSection = Backbone.Model.extend({
defaults: {
title: '',
position: ''
},
initialize: function(){
},
renderView: function(){
return "<li>"+this.get('title')+", Position: "+this.get('position')+"</li>"
}
});
PaperSectionsList = Backbone.Collection.extend({
url: '/admin/paper/section/list.html',
size: 6,
initialize: function(){
this.add(new PaperSection({
id:1,
title: "Hello World",
position:1
}));
},
comparator: function(section){
return section.get('position');
},
renderView: function(){
var html = "<ul>";
_.each(this.models, function(section){
html += section.renderView();
});
if(_.size(this.models) < this.size){
html+="<li><a href='#add_section' class='btn btn-success btn-small' id='add_section'>Add Section</a></li>"
}
html+="</ul>";
return html;
}
});
PaperSectionView = Backbone.View.extend({
initialize: function(){
this.render();
},
render: function(){
console.log(this.collection.get(1));
var html = this.collection.renderView();
this.$el.html(html);
}
});
var paper_sections = new PaperSectionsList({
model: PaperSection,
});
var section_view = new PaperSectionView({
collection: paper_sections,
el: $('#paper_sections')
});
When I run the code I get the error that section.renderView() is not a function. Need help with this. How do I iterate over models in my collection?

Your first problem is that you are defining your collection and instantiating it incorrectly.
The model declaration needs to happen in the collection's definition, not in the instantiation:
PaperSectionsList = Backbone.Collection.extend({
model: PaperSection,
And then you just instantiate it:
var paper_sections = new PaperSectionsList();
That will get your code working.
But, I feel compelled to point out that you have some confusion about your coding concerns. Models and Collections should never have functions called render*. These are View concerns. In your case, the idiomatic way of handling it would be to have to views: PaperSectionListView (ul) and PaperSectionListItem (li). The templates and render functions live in those views.

I've got your code working as follows...
But I think the above answer handles the core issue, and I agree with the suggestions that the Models and Collections should not be handling render logic.
Note: I cleaned up some JSLint errors such as and extra comma and missing semicolons.
var PaperSection = Backbone.Model.extend({
defaults: {
title: '',
position: ''
},
initialize: function () {
},
renderView: function () {
return "<li>" + this.get('title') + ", Position: " + this.get('position') + "</li>";
}
});
var PaperSectionsList = Backbone.Collection.extend({
url: '/admin/paper/section/list.html',
model: PaperSection,
size: 6,
initialize: function () {
this.add(new PaperSection({
id: 1,
title: "Hello World",
position: 1
}));
},
comparator: function (section) {
return section.get('position');
},
renderView: function () {
var html = "<ul>";
_.each(this.models, function (section) {
html += section.renderView();
});
if (_.size(this.models) < this.size) {
html += "<li><a href='#add_section' class='btn btn-success btn-small' id='add_section'>Add Section</a></li>";
}
html += "</ul>";
return html;
}
});
var PaperSectionView = Backbone.View.extend({
initialize: function () {
this.render();
},
render: function () {
console.log(this.collection.get(1));
var html = this.collection.renderView();
this.$el.html(html);
}
});
$(function () {
var paper_sections = new PaperSectionsList({
model: PaperSection
});
var section_view = new PaperSectionView({
collection: paper_sections,
el: $('#paper_sections')
});
});

Related

How to reference a Backbone/Marionette View within itself?

MyView.js:
define(['app/models/MyModel'],
function (MyModel) {
return Mn.LayoutView.extend({
template: '#my-template',
className: 'my-classname',
regions: {
content: '.content-region',
panel: '.panel-region'
}
initialize: function () {
_.bindAll(this, 'childButtonClicked');
},
onShow: function () {
this.getRegion('content').show(new AnotherView());
},
childEvents: {
'some-child-click': 'childButtonClicked'
},
childButtonClicked: function (view) {
var newView = new MyView({
model: new MyModel({
title: view.model.get('title')
})
});
this.getRegion('panel').show(newView);
}
});
});
I'm trying to nest instances of MyView within itself. This worked correctly when I was building the prototype by dumping everything into one function, like so:
var MyView = Mn.LayoutView.extend({
...
childButtonClicked: function(view) {
var newView = new MyView({
...
Now that I'm trying to separate the Views into their own files and use require.js, I can't figure out the syntax for a self-referential view.
When I run this code as is, I get an error like 'MyView is undefined'.
If I add it to the require header like so:
define(['app/models/MyModel', 'app/views/MyView'],
function (MyModel, MyView) {
I get the error 'MyView is not a function'.
EDIT for solution:
The marked solution works fine, I ended up using the obvious-in-hindslght:
define(['app/models/MyModel'],
function (MyModel) {
var MyView = Mn.LayoutView.extend({
template: '#my-template',
className: 'my-classname',
regions: {
content: '.content-region',
panel: '.panel-region'
}
initialize: function () {
_.bindAll(this, 'childButtonClicked');
},
onShow: function () {
this.getRegion('content').show(new AnotherView());
},
childEvents: {
'some-child-click': 'childButtonClicked'
},
childButtonClicked: function (view) {
var newView = new MyView({
model: new MyModel({
title: view.model.get('title')
})
});
this.getRegion('panel').show(newView);
}
});
return MyView;
});
You can require() in your module: var MyView = require(app/views/MyView);.
So for want of a better place:
childButtonClicked: function (view) {
var MyView = require(app/views/MyView);
var newView = new MyView({
model: new MyModel({
title: view.model.get('title')
})
});
this.getRegion('panel').show(newView);
}

Backbonejs - in collection, how do we access this.var from initialize function?

What I am trying to accomplish is pass this Post model ID to a collection so I can populate this specific Post id with other models that are associated with it. For example: In a Blog Post, contains a bunch of Comments. and I want to display those comments pointing to this Blog Post only.
I must be missing something fundamental here.
Below is my Post Model and i am instantiating a new CommentCollection and passing along models and options arguments.
var PostModel = Backbone.Model.extend({
initialize: function() {
/*
* we are connecting Comments collection
* to each post item by passing along post id
* */
this.comments = new CommentsCollection([], { id: this.id });
},
defaults: {
title: 'title here',
body: 'body here'
},
urlRoot: localserver + "/posts"
});
Below is my Comment Collection. console.log(this.id); returns undefined.
var CommentsCollection = Backbone.Collection.extend({
initialize: function(models, options) {
this.id = options.id;
console.log(this.id);
return this;
},
url: function() {
console.log(this.id);
return localserver + "/posts/" + this.id + "/comments";
},
model: CommentModel
});
my console is returning this:
Uncaught TypeError: Cannot read property 'id' of undefined
3
Try this code:
var CommentModel = Backbone.Model.extend({});
var CommentsCollection = Backbone.Collection.extend({
model: CommentModel,
initialize: function(models, options) {
this.id = options.id;
if(typeof this.id === 'undefined') { return; }
this.url();
},
url: function() {
var localserver = "localhost";
console.log('from comment url: ', this.id);
return localserver + "/" + this.id + "/comments";
}
});
var PostModel = Backbone.Model.extend({
urlRoot: "http://jsonplaceholder.typicode.com" + "/posts",
initialize: function(option) {
this.comments = new CommentsCollection([], { id: option.id });
}
});
//var pm = new PostModel();
//pm.comments.fetch();
//console.log('from pm: ', pm.comments.url());
var PostsCollection = Backbone.Collection.extend({
model: PostModel,
url: "http://jsonplaceholder.typicode.com" + "/posts?_sort=views&_order=DESC",
initialize: function() {
this.on('reset', this.getComments, this);
},
getComments: function() {
this.each(function(post) {
post.comments = new CommentsCollection([], { post: post });
post.comments.fetch();
});
}
});
var pc = new PostsCollection();
pc.fetch();
What I did is that I use the option parameter of the PostModal. Below is the code.
var PostModel = Backbone.Model.extend({
urlRoot: "http://jsonplaceholder.typicode.com" + "/posts",
initialize: function(option) {
this.comments = new CommentsCollection([], { id: option.id });
}
});

Issues searching backbone collection

I have this bb app that I'm trying to search and return the results of the search, then when cleared, so all results again. I was able to get everything to show before adding the search feature, but now nothing showing up. I think the collection isn't available at the time it's trying to populate, but can't seem to get it to wait. I've tried moving the fetch around to no avail. Any help would be greatly appreciate. For the sake of ease, I've put everything in a fiddle that can be found here...
//Campaign Model w defaults
app.model.Campaign = Backbone.Model.extend({
default: {
title: '',
img: '',
id: '',
slug: '',
image_types: 'small',
tagline: ''
}
});
//Campaign Collection from model
app.collection.Campaign = Backbone.Collection.extend({
//our URL we're fetching from
url: 'https://api.indiegogo.com/1/campaigns.json?api_token=e377270bf1e9121da34cb6dff0e8af52a03296766a8e955c19f62f593651b346',
parse: function(response) {
console.log('parsing...');
return response.response; //get array from obj to add to collection based on model
},
currentStatus: function(status){
return _(this.filter(function(data){
console.log('currentStats', status);
return data.get('_pending') == status;
}));
},
search: function(searchVal) {
console.log('search...');
if (searchVal == '') {
return this;
}
var pattern = new RegExp(searchVal, 'gi');
return _(this.filter(function(data) {
return pattern.test(data.get('title'));
}));
}
});
app.collection.campaigns = new app.collection.Campaign();
app.collection.campaigns.fetch({
success: function(){
console.log('Success...');
var sHeight = window.screen.availHeight - 200 + 'px';
$('#container ul').css('height', sHeight);
},
error: function() {
console.log('error ',arguments);
}
});
//List view for all the campaigns
app.view.CampaignList = Backbone.View.extend({
events: {
'keyup #searchBox': 'search'
},
render: function(data) {
console.log('campaignList',$(this.el).html(this.template));
$(this.el).html(this.template);
return this;
},
renderAll: function(campaigns) {
console.log('campList renderAll', campaigns, $('#campaignList'));
$('#campaignList').html('');
campaigns.each(function(campaign){
var view = new app.view.CampaignItem({
model: campaign,
collection: this.collection
});
console.log(view);
$('#campaignList').append(view.render().el);
});
return this;
},
initialize: function() {
console.log('init campList',app);
this.template = _.template($('#campaignList-tmp').html());
this.collection.bind('reset', this.render, this);
},
search: function(e) {
console.log('listView search');
var searchVal = $('#searchBox').val();
this.renderAll(this.collection.search(searchVal));
},
sorts: function() {
var status = $('#campSorting').find('option:selected').val();
if(status == '') {
status = false;
};
this.renderAll(this.collection.currentStatus(status));
}
});
//Item view for single campaign
app.view.CampaignItem = Backbone.View.extend({
events: {},
render: function(data){
console.log('campItem render...', data);
this.$el.html(this.template(this.model.toJSON()));
return this;
},
initialize: function(){
console.log('campItem init');
this.template = _.template( $('#campaignItem-tmp').html());
}
});
//Router
app.router.Campaign = Backbone.Router.extend({
routes: {
'': 'campaigns'
},
campaigns: function(){
this.campListView = new app.view.CampaignList({
collection: app.collection.campaigns
});
$('#container').append(this.campListView.render().el);
this.campListView.sorts();
}
});
app.router.campaigns = new app.router.Campaign();
Backbone.history.start();
http://jsfiddle.net/skipzero/xqvrpyx8/

Backbone.js master-detail view, navigation issue (jsfiddle included)

I'm struggling to get a simple master-detail scenario working with Backbone. Here's the jsfiddle and code is below.
Problem 1: this navigation doesn't work at all if I switch "pushstate" to true. What I really want is to have no hashes/pound signs in my urls.
Problem 2: my users might rock up on a url like /accommodation/287, not always on the home page. How would you deal with that using the router?
Thanks a lot for any help!
var AccommodationItem = Backbone.Model.extend({
defaults: {
html: "",
loaded: false
},
urlRoot: "/Home/Accommodation/"
});
var AccommodationItemView = Backbone.View.extend({
tagName: "li",
template: _.template("<a href='#accommodation/<%= id %>'><%= description %></a>"),
render: function () {
this.$el.html(this.template(this.model.toJSON()));
return this;
}
});
var AccommodationList = Backbone.Collection.extend({
model: AccommodationItem
});
var DetailView = Backbone.View.extend({
initialize: function () { },
render: function () {
this.$el.html(this.model.get("html"));
},
setModel: function (model) {
this.model = model;
var $this = this;
if (!this.model.get("loaded")) {
/*
this.model.fetch({ success: function () {
$this.model.set("loaded", true);
$this.render();
}
});*/
$this.model.set("html", "<h2>Full item " + this.model.get("id") + "</h2>");
$this.model.set("loaded", true);
$this.render();
} else {
$this.render();
}
}
});
var AccommodationListView = Backbone.View.extend({
tagName: "ul",
initialize: function () {
this.collection.on("reset", this.render, this);
},
render: function () {
this.addAll();
},
addOne: function (item) {
var itemView = new AccommodationItemView({ model: item });
this.$el.append(itemView.render().el);
},
addAll: function () {
this.collection.forEach(this.addOne, this);
}
});
var App = new (Backbone.Router.extend({
routes: {
"": "index",
"accommodation/:id": "show"
},
initialize: function () {
this.detailView = new DetailView({ model: new AccommodationItem({ id: 1 }) });
$("#detail").append(this.detailView.el);
this.accommodationList = new AccommodationList();
this.accommodationListView = new AccommodationListView({ collection: this.accommodationList });
$("#app").append(this.accommodationListView.el);
},
start: function () {
Backbone.history.start({ pushState: false });
},
index: function () {
this.fetchCollections();
},
show: function (id) {
var model = this.accommodationList.get(id);
this.detailView.setModel(model);
},
fetchCollections: function () {
var items = [{ id: 1, description: "item one" }, { id: 2, description: "item two" }, { id: 3, description: "item three" }];
this.accommodationList.reset(items);
}
}));
$(function () {
App.start();
});
EDIT: In a comment below I mentioned the Codeschool backbone.js tutorial. Just want to say that I have now finished BOTH parts of the course and it DOES cover exactly the AppView pattern described in the accepted answer. It's an excellent course and I thoroughly recommend it.
you have a few of the concepts mixed up.
There is too much to explain here, so I've (very roughly) put together a patch of your code that works as you intend. I would advise that you put it side-by-side with your own and see what I have done differently.
http://jsfiddle.net/wtxK8/2
A couple of things, you should not init Backbone.history from within a router. your 'init' should look something more like this
$(function () {
window.app = new App();
window.appView = new AppView({el:document});
Backbone.history.start({ pushState: true });
});
This is setting a 'wrapper' view than encompasses the entire page. Also, you have far too much logic in your router. Try to only use the router for routes. After my quick re factor, your router only contains this:
var App = Backbone.Router.extend({
routes: {
"": "index",
"accommodation/:id": "show"
},
show: function (id) {
var model = window.appView.accommodationList.get(id);
window.appView.detailView.setModel(model);
}
});
The AppView (that I have written for you now does all of that initialize work.
var AppView = Backbone.View.extend({
initialize : function(){
this.detailView = new DetailView({ model: new AccommodationItem({ id: 1 }) });
$("#detail").append(this.detailView.el);
this.accommodationList = new AccommodationList();
this.accommodationListView = new AccommodationListView({ collection: this.accommodationList });
$("#app").append(this.accommodationListView.el);
this.fetchCollections();
},
fetchCollections: function () {
var items = [
{ id: 1, description: "item one" },
{ id: 2, description: "item two" },
{ id: 3, description: "item three" }
];
this.accommodationList.reset(items);
}
});
Even after my re factor, it's still far from optimal, but I have provided it all to help you on your journey of learning :)
I would then recommend you follow some of the on-line tutorials step-by-step so that you can set up the structure of your app in a better way.
Good Luck, and be sure to check out http://jsfiddle.net/wtxK8/2 to see it working.
EDIT: I have not address your second question. there is enough to be worked on with question 1 to keep you busy. If I have more time later, I will help further.

backbone view render not creating

Just beginning with backbone and after few hours can't seem to get even a view render working correctly. I've included all appropriate JavaScript files in HTML. Here is my script:
(function($) {
// MODELS
var Paper = Backbone.Model.extend ({
defaults : {
title : null,
author: null,
}
});
// COLLECTIONS
var PaperCollection = Backbone.Collection.extend({
model : Paper,
initialize : function() {
console.log("We've created our collection");
}
});
// VIEWS
var PaperView = Backbone.View.extend({
tagName:'li',
className: 'resultTable',
events: {
'click .ptitle':'handleClick'
},
initialize: function() {
_.bindAll(this, 'render', 'handleClick');
},
render: function() {
$(this.el).html('<td>'+this.model.get('title')+'</td>');
return this; // for chainable calls
},
handleClick: function() {
alert('Been clicked');
}
});
var ListView = Backbone.View.extend({
events: {
//"keypress #new-todo": "createOnEnter",
},
initialize : function() {
console.log('Created my app view');
_.bindAll(this, 'render', 'addOne', 'appendOne');
this.collection = new PaperCollection();
this.collection.bind('add', this.appendOne); // collection event binder
this.counter = 0;
this.render();
},
render : function() {
console.log('Render app view');
$(this.el).append("<button id='add'>Add list item</button>");
$(this.el).append("<p>More text</p>");
// $(this.el).append("<ul></ul>");
/*
_(this.collection.models).each(function(item){ // in case collection is not empty
appendOne(item);
}, this); */
},
addOne: function() {
this.counter++;
var p = new Paper();
p.set({
title: "My title: " + this.counter // modify item defaults
});
this.collection.add(p);
},
appendOne: function(p) {
var paperView = new PaperView({
model: p
});
$('ul', this.el).append(paperView.render().el);
}
});
var App = new ListView({el: $('paper_list') });
// App.addOne();
})(jQuery);
Note not getting any errors in console on FF - but still not displaying any of the render outputs in AppView). Appreciate any help. Simple HTML:
<body>
<div class="container_16">
<div class="grid_16">
<div id="paper_list">
Text...
<ul class="thelist"></ul>
</div>
</div>
<div class="clear"></div>
</div>
</body>
This will at least get you rendering the ListView...
// MODELS
var Paper = Backbone.Model.extend ({
defaults : {
title : null,
author: null,
}
});
// COLLECTIONS
var PaperCollection = Backbone.Collection.extend({
model : Paper,
initialize : function() {
console.log("We've created our collection");
}
});
// VIEWS
var PaperView = Backbone.View.extend({
tagName:'li',
className: 'resultTable',
events: {
'click .ptitle':'handleClick'
},
initialize: function() {
_.bindAll(this, 'render', 'handleClick');
},
render: function() {
$(this.el).html('<td>'+this.model.get('title')+'</td>');
return this; // for chainable calls
},
handleClick: function() {
alert('Been clicked');
}
});
var ListView = Backbone.View.extend({
el: '#paper_list',
events: {
"click #add": "createOnEnter",
},
initialize : function() {
console.log('Created my app view');
_.bindAll(this, 'render', 'addOne', 'appendOne');
this.collection = new PaperCollection();
this.collection.bind('add', this.appendOne); // collection event binder
this.counter = 0;
this.render();
},
render : function() {
console.log(this);
$(this.el).append("<button id='add'>Add list item</button>");
$(this.el).append("<p>More text</p>");
// $(this.el).append("<ul></ul>");
/*
_(this.collection.models).each(function(item){ // in case collection is not empty
appendOne(item);
}, this); */
},
addOne: function() {
this.counter++;
var p = new Paper();
p.set({
title: "My title: " + this.counter // modify item defaults
});
this.collection.add(p);
},
appendOne: function(p) {
var paperView = new PaperView({
model: p
});
$('ul', this.el).append(paperView.render().el);
}
});
$(function(){
var App = new ListView();
});
A couple of things...First, I initialized your ListView inside of a document.ready to make sure that the DOM was ready to go, second, I made the el in the listview simply #paper_list then you can do $(this.el) later.
I at least got the button and "more text" to show up...Let me know if that helps!

Categories

Resources