I seem to have run into a problem,
Can anyone spot why nothing is put into the div id "app"??
http://jsfiddle.net/suN5n/1/
From the link:
JS -
var MyModel = Backbone.Model.extend();
var data = {
first: 'hello',
second: 'there'
};
var myModel = new MyModel(data);
var MyView = Backbone.View.extend({
model: myModel,
el: $('#app'),
initialize: function(){
this.render();
},
render: function(){
this.$el.html(this.model.toJSON());
}
});
var myView = new MyView();
I'm sure it's just me being a BackboneJS noob.
oh, you're trying to pass an object to .html when it only takes a string. Use JSON.stringify(this.model.toJSON()) if you want to do something weird like that.
Related
I'm using Backbone.js 1.1.2 with jQuery 1.11.
I think I'm missing something very simple.
I've set up a model: Contact and a collection: ContactList.
I have success rendering ContactView views with models obtained from ContactList.fetch()
But when I try to bind a change event in the view initializer:
this.model.on('change', this.render, this);
... I get:
backbone Uncaught TypeError: Object #<Object> has no method 'on'
So I go back to the documentation and read that .on() is part of Backbone.Events, and somehow I need to extend my model to use this (and why is this not part of the out-of-the-box functionality for models??)
I've tried declaring
_.extend(this, Backbone.Events);
... inside of the initialize function for ContactView before declaring the binding, but no dice.
What do I need to do to get the Backbone.Events functionality working??
update: all relevant code (sans template)
var Contact = Backbone.Model.extend({
defaults: {
FirstName: '',
...[etc]
},
initialize: function() {
}
});
var ContactList = Backbone.Collection.extend({
model: Contact,
url: '/api/Contacts/getall'
});
var ContactView = Backbone.View.extend({
initialize: function() {
this.render();
this.model.on('change', this.render, this);
},
render: function () {
var template = _.template($('#contact_template').html(), this.model);
this.$el.html(template);
}
});
var contactList = new ContactList();
contactList.fetch({
success: function (collection, response, options) {
contactList.models.forEach(function (m) {
var $el = $('<li />')
.addClass('adminItem clearfix')
.appendTo($('#contactList'));
new ContactView({ el: $el, model: m.attributes });
});
}
});
This line is the source of trouble
new ContactView({ el: $el, model: m.attributes });
m.attributes is a plain js object. You want to pass along the actual model object:
new ContactView({ el: $el, model: m});
And because the collection has underscore methods mixed in you should be able to simplify it a bit to this:
contactList.each(function (m) {
var $el = $('<li />')
.addClass('adminItem clearfix')
.appendTo($('#contactList'));
new ContactView({ el: $el, model: m});
});
I am trying to understand the relationship between a model and a view. I've tried building a model and view to render that model.
I get the error Cannot call method 'toJSON' of undefined which I understand as the actual instance of the model is not being sent to the view.
I feel there is something missing in the initialize of the view?
The Model:
var sticky = Backbone.Model.extend({
defaults: {
title:"",
content:"",
created: new Date()
},
initialize: function() {
console.log("sticky created!");
}
});
The View:
var stickyView = Backbone.View.extend({
tagName:"div",
className:"sticky-container",
initialize: function() {
this.render();
console.log("stickyView created!");
},
render: function() {
$("#content-view").prepend(this.el);
var data = this.model.toJSON(); // Error: Cannot call method 'toJSON' of undefined
console.log(data);
var source = $("#sticky-template").html();
var template = Handlebars.compile(source);
$(this.el).html(template(data));
return this;
}
});
Creating new model and new instance of the view:
var Sticky = new sticky({title:"test"});
var StickyView = new stickyView();
You have to pass your model instance to your view, Backbone will do the rest:
constructor / initialize new View([options])
There are several special options that, if passed, will be attached directly to
the view: model, collection, el, id, className, tagName and
attributes.
which means you would create your view like this
var StickyView = new stickyView({model: Sticky});
And while you're at it, you could pass your compiled template and the DOM node you wish to set as your view element (and remove the tagName and className from your view definition) to avoid a strict coupling:
var stickyView = Backbone.View.extend({
initialize: function(opts) {
this.template = opts.template;
this.render();
console.log("stickyView created!");
},
render: function() {
var data = this.model.toJSON();
console.log(data);
this.$el.html(this.template(data));
return this;
}
});
var StickyView = new stickyView({
model: Sticky,
el: '#content-view',
template: Handlebars.compile($("#sticky-template").html())
});
Looking for the absolute minimum script to get Backbone working. Tried piecing various tutorials and sample together, but having problems getting views to work. Nothing fancy, I'll take raw json in the browser right now. Just a basic skeleton to help connect the dots and build on. I've tried various variations on the following:
(function ($) {
var model = Backbone.Model.extend({
idAttribute: 'custId'
});
var collection = Backbone.Collection.extend({
initialize: function(){
},
model: model,
url: '/cust'
});
var view = Backbone.View.extend({
initialize: function(){
_.bindAll(this, 'render'); // fixes loss of context for 'this' within methods
this.collection.bind("reset", this.render);
this.render();
},
el: $('#content'),
template: Handlebars.compile($("#contentTemplate").html()),
render: function(){
$(this.el).html( this.template(this.model.toJSON()));
},
tagName: "li"
});
var router = Backbone.Router.extend({
initialize: function(){
var newCollection = new collection;
newCollection.fetch();
},
route: {
"": "home"
},
home: function(){
this.view = new view({collection: newCollection});
$('#content').html(this.view.el);
}
});
var app = new router();
}(jQuery))
Thanx.
You are misusing the el attribute. $('#content').html(this.view.el) will result in copying the $('#content') element inside itself (because view.el is equal to $('#content')).
You should try removing the el attribute from the view object and let it generate itself. Then $('#content').html(this.view.el); should work.
One other possible problem is that you are rendering the entire collection inside a li element - was this what you are going for? The best way to go about this would be to have each model in the collection represent a li tag and the collection a ul tag.
Other issues:
the view element is receiving a collection but you are trying to render a model
in the router, newCollection is not accessible in the home method
You are not calling Backbone.history.start()
Here is how i would rewrite the code:
var model = Backbone.Model.extend({
idAttribute: 'custId'
});
var model_view = Backbone.View.extend({
template: Handlebars.compile($("#modelTemplate").html()),
tagName: 'li',
initialize: function() {
_.bindAll(this, 'render');
this.render();
this.on('change',this.render);
},
render: function() {
$(this.el).html( this.template(this.model.toJSON()) );
return this;
}
});
var collection = Backbone.Collection.extend({
initialize: function(){
},
model: model,
url: '/cust'
});
var collection_view = Backbone.View.extend({
tagName: "ul",
initialize: function(){
_.bindAll(this, 'render','renderModels');
this.render();
this.renderModels();
this.collection.bind("reset", this.render);
this.collection.bind("reset", this.renderModels);
},
render: function(){
// just create the 'ul' tag; we will populate it with model view elements; a collection template is no longer needed
return this;
},
renderModels: function() {
this.collection.each(function(obj){
var view = new model_view({
model: obj
});
$(this.el).append(view.el);
},this);
}
});
var router = Backbone.Router.extend({
initialize: function(){
this.newCollection = new collection();
this.newCollection.fetch();
},
route: {
"": "home"
},
home: function(){
this.view = new collection_view({collection: this.newCollection});
$('#content').html(this.view.el); // #content should not be a 'ul' tag, the 'ul' is generated by the collection_view
}
});
var app = new router();
Backbone.history.start();
Make sure you update your templates accordingly.
Please excuse possible errors, i had no means to test the code but i believe it points out the logic you should use.
Cheers!
I'm relatively new to Backbone and Underscore and have one of those questions that's not really an issue - just bugging me out of curiosity.
I built a very simple app that allows you to add and remove models within a collection and renders them in the browser. It also has the ability to console.log the collection (so I can see my collection).
Here's the weird thing: the ID's being generated are 1,3,5... and so on. Is there a reason specific to my code, or something to do with BB/US?
Here's a working Fiddle: http://jsfiddle.net/ptagp/
And the code:
App = (function(){
var AppModel = Backbone.Model.extend({
defaults: {
id: null,
item: null
}
});
var AppCollection = Backbone.Collection.extend({
model: AppModel
});
var AppView = Backbone.View.extend({
el: $('#app'),
newfield: $('#new-item'),
initialize: function(){
this.el = $(this.el);
},
events: {
'click #add-new': 'addItem',
'click .remove-item': 'removeItem',
'click #print-collection': 'printCollection'
},
template: $('#item-template').html(),
render: function(model){
var templ = _.template(this.template);
this.el.append(templ({
id: model.get('id'),
item: model.get('item')
}));
},
addItem: function(){
var NewModel = new AppModel({
id: _.uniqueId(),
item: this.newfield.val()
});
this.collection.add(NewModel);
this.render(NewModel);
},
removeItem: function(e){
var id = this.$(e.currentTarget).parent('div').data('id');
var model = this.collection.get(id);
this.collection.remove(model);
$(e.target).parent('div').remove();
},
printCollection: function(){
this.collection.each(function(model){
console.log(model.get('id')+': '+model.get('item'));
});
}
});
return {
start: function(){
new AppView({
collection: new AppCollection()
});
}
};
});
$(function(){ new App().start(); });
if you look in the backbone.js source code you'll notice that _.uniqueId is used to set a model's cid:
https://github.com/documentcloud/backbone/blob/master/backbone.js#L194
that means that every time you create a model instance, _.uniqueId() is invoked.
that's what causing it to increment twice.
Here is my Model View and Collection :
window.Report = Backbone.Model.extend({});
window.ReportCollection = Backbone.Collection.extend({
model: Report,
initialize: function(properties){
this.url = properties.url;
}
});
window.ReportCollectionView = Backbone.View.extend({
initialize: function(){
this.collection.reset();
this.render();
},
render: function(){
var self = this;
this.collection.fetch({
success: function(){
self.collection.each(function(model){
//pass model to subview
});
}
}
});
}
});
in the other part of the code I use the instantiate the above objects
var reportCollection = new ReportCollection({url:someURL});
var reportCollectionView = new ReportCollectionView({collection:reportCollection});
'someURL' is a REST based URL that returns JSON list of Objects
So far everything looks good. What I am trying to achieve is:
I must be able to refresh the 'reportCollection' by changing the url and this should trigger an updated 'reportCollectionView'. Thanks for any pointers
I suppose you could add a method to your collection which changes url and forces a fetch:
window.ReportCollection = Backbone.Collection.extend({
//...
changeUrl: function(url) {
this.url = url;
this.fetch();
}
});
and then bind to the "reset" event in your view:
window.ReportCollectionView = Backbone.View.extend({
initialize: function() {
_.bindAll(this, 'render');
this.collection.on('reset', this.render);
this.collection.reset();
},
//...
});
Then if you do this:
c = new ReportCollection(...);
v = new ReportCollectionView({ collection: c, ... });
You'll get your rendered view and then later you can:
c.changeUrl(...);
to set the new URL and that will trigger a render call on v.