I'm having trouble creating an instance of a CollectionView in Marionette 2.4.1. This is a really simple example. Not sure what's going on. Seems like a Marionette bug.
var AppointmentModel = Backbone.Model.extend({
});
var AppointmentCollection = Backbone.Collection.extend({
model: AppointmentModel
});
var TimeSlotView = Marionette.ItemView.extend({
});
var AppointmentCollectionView = Marionette.CollectionView.extend({
childView: TimeSlotView
});
Here is the template:
<script id="time-slot-template" type="text/template">
<div class="col-xs-4">
<button class="btn-time-slot" data-appointment-time="<%= date %>">
<%= date %>
</button>
</div>
</script>
The main code:
//...inside ajax success
// result looks like this:
// [ "2015-1-2", "2015-1-17", "2015-2-1", "2015-2-8" ]
var appointmentCollection = new AppointmentCollection(result.map(function(apt){
return new AppointmentModel({ date: apt });
})); //browser console shows a good valid collection
var appointmentCollectionView = new AppointmentCollectionView({ collection: appointmentCollection });
// Undefined is not a function
What am I doing wrong?
Doesn't look like there's anything wrong with the code you posted.
Can confirm it works fine for me...http://jsfiddle.net/nuewwdmr/
The only change i made was to include the idAttribute in the model but that wouldn't effect this issue you're having.
var AppointmentModel = Backbone.Model.extend({
idAttribute: 'date'
});
The only thing I can think of is that Marionette has dependencies on Babysitter and Wreqr (as well as underscore, Backbone obviously). Have you included these dependencies?
Related
I am new bee to backbone and i am trying with a below sample
http://jsfiddle.net/naveencgr/L3orucjm/
While loading i am getting this.el.html is not a function, let me know what is the cause of it.
HTML:
<div id="container"></div>
<script type="text/template" id="template">
<input type="text" name="Name" id="Name"></input>
<input type="button" name="Add" id="Add" value="Add"></input>
<div id="list"></div>
</script>
JavaScript:
NameModel = Backbone.Model.extend({
});
var nameModel = new NameModel();
nameModel.set('name','test');
alert(nameModel.get('name'));
NameView = Backbone.View.extend({
tagName:"li",
render: function(){
var template=_.template("<%=name%>", nameModel.toJSON());
return template;
}
});
var nameView = new NameView();
NameViewList = Backbone.View.extend({
initialize: function(){
this.render();
},
render: function(){
var template = _.template($("#template").html(), {});
this.el.html(template);
},
events : {
"click input#Add" :"addName",
},
addName : function() {
var name = $("#Name").val();
if (name.trim().length > 0) {
//nameModel.set("name", name);
$("#list").append(nameView.render());
}
}
});
var nameViewList = new NameViewList({el : $("div#container")});
You have numerous errors in your code. You know about one of them but not the others.
The one you know about is:
this.el.html(template);
A view's el is just a DOM node and that doesn't have an html function. The html function you're trying to use is part of jQuery so you'd want to call it on this.$el (which is just a cached version of $(this.el)):
this.$el.html(template);
Other problems:
Your fiddle is missing vars all over the place; don't say:
NameModel = ...
say:
var NameModel
to avoid accidental globals.
Your NameView is strange. It has tagName: 'li' so presumably it should be creating list elements but the render doesn't do anything with the view's el, it just returns a string of HTML that ends up inside a <div>. That <div> should be a <ul>:
<ul id="list"></ul>
A render function generally populates the view's el and, to allow chaining, returns this:
render: function() {
var template = _.template('<%= name %>');
this.$el.html(template(nameModel.toJSON()));
return this;
}
You are using Underscore's _.template incorrectly. You used to be able to say:
var h = _.template(template_source, data);
to compile the template and fill it in in one step but as of Underscore 1.7, the second argument to _.template is an options object. Now you need to compile and fill in the template in separate steps:
var t = _.template(template_source);
var h = t(data);
You'll see this change in the render above.
The way you're using your NameView is strange. Apparently you are trying to use one NameView to handle multiple names, this would work with your strange NameView#render implementation but it will fall apart once NameView has anything to do or once NameView is updated (as above) to be more conventional. You should create one NameView for each name you're displaying and each NameView should have its own NameModel as its model property. This would make NameView#render look like:
render: function() {
var template = _.template('<%= name %>');
this.$el.html(template(this.model.toJSON()));
return this;
}
and NameViewList#addName would look like:
addName: function() {
var name = this.$("#Name").val();
if(name.trim().length > 0) {
var nameView = new NameView({
model: new NameModel({ name: name })
});
this.$('#list').append(nameView.render().el);
}
}
You'll note that we're using NameView#render's new return value, this x.append(v.render().el) pattern is quite common and idiomatic in Backbone so it is a good practice. You should also notice that the search for #list is now limited to the view's el by using the view's this.$ function; this.$('#list') is equivalent to this.$el.find('#list') and doing things this way helps you keep your views self-contained.
In real life you'd probably put your new NameModel({ name: name }) instances in a collection somewhere and events on that collection would trigger the creation of new NameViews.
Applying all that to your fiddle gives you this functional and more idiomatic version:
http://jsfiddle.net/ambiguous/8x0ma9qo/
I will explain my problem with an example. I can make this piece of code work without any problem (using MarionetteJS v1.6.2):
http://codepen.io/jackocnr/pen/tvqHa
But when I try to use it with requireJs and I put it on the initialize method of a Marionette Controller, I'm Getting the following error:
Uncaught TypeError: undefined is not a function backbone.marionette.js:2089
The Error comes when I define the collection view:
var userListView = new UserListView({
collection: userList
});
I Can't figure out what is happening (this is the same code of the link above, but inside the controller initialize method)
define([
'jquery',
'underscore',
'backbone',
'marionette'
], function($,_,Backbone,Marionette){
var Controller = Backbone.Marionette.Controller.extend({
initialize: function(){
var User = Backbone.Model.extend({});
var UserList = Backbone.Collection.extend({
model: User
});
var UserView = Backbone.Marionette.ItemView.extend({
template: _.template($("#user-template").html())
});
var UserListView = Backbone.Marionette.CollectionView.extend({
tagName: "ul",
itemView: UserView,
initialize: function() {
this.listenTo(this.collection, "add", this.render);
}
});
// instances
var jack = new User({name: "Jack"});
var userList = new UserList(jack);
var userListView = new UserListView({
collection: userList
});
// add to page
$("#user-list").append(userListView.render().el);
$("#add-user").click(function() {
var andy = new User({name: "Andy"});
userList.add(andy);
});
},
});
return Controller;
});
instead of using Backbone.Marionette in main.js shim : { use Marionette
marionette: {
exports: 'Marionette',
deps: ['backbone']
},
Thus while declaring any marionette inheritance juste use Marionette instead of Backbone.Marionette as such
var Controller = Marionette.Controller.extend
var UserView = Marionette.ItemView.extend
var UserListView = Marionette.CollectionView.extend
For some reason the newer version or Marionette.js behave this way. I guest it produce less code.
I have replaced the Marionette 1.6.2 version with the 1.5, and now it works as it does the version without requireJs. So I think it's a release bug or something like that.
Seems to be working fine for me. I made a simple project here.
I am new to backbonejs. What I am trying to do is, render a template on page load and pass model as data parameter in _.template function. Here is my bacbone code:
var Trip = Backbone.Model.extend({
url: '/trips/' + trip_id + '/show'
});
var InviteTraveller = Backbone.View.extend({
el: '.page',
render: function () {
var that = this;
var trip = new Trip();
trip.fetch({
success: function(){
console.log(trip); //logs trip object correctly
var template = _.template($('#invite-traveller-template').html(), {trip: trip});
that.$el.html(template);
}
});
}
});
var Router = Backbone.Router.extend({
routes: {
'': 'fetchTrip'
}
});
var inviteTraveller = new InviteTraveller();
var router = new Router();
router.on('route:fetchTrip',function () {
inviteTraveller.render();
});
Backbone.history.start();
And here is my sample template:
<script type="text/template" id="invite-traveller-template">
<h3>Trip</h3>
<h3><%= trip.get('name') %></h3>
</script>
On running, I am getting the this in browser window and console shows:
trip is not defined
I am facing this issue since yesterday but could not figure out the solution yet. Not understanding what is going wrong, code also seems to be right. Any help would be greatly appreciated.
Update:
I removed
inviteTravellers.render();
from router.on() and then reloaded the page in browser. I still got same error which means that <script></script> (template) is being compiled before calling render() of InviteTraveller view. What can be the possible reason for this?
I had the same issue (underscore v1.8.2). My fix:
var template = _.template($('#invite-traveller-template').html());
var compiled = template({trip: trip});
that.$el.html(compiled);
You're passing the whole model to the template. Typically you would call model.toJSON and then pass its result to the template. Additionally using <%= in your template to render the attribute, which is meant for interpolating variables from that JSON object you're passing.
You can pass a whole model to the template and use <% ... %> to execute pure Javascript code and use print to get the attribute but it's probably overkill.
Have a look at this fiddle.
You code work perfectfly, here's it
I think that your problem came from another code, not the one you have posted, because there's no way for your view to render if you remove :
inviteTravellers.render();
Try to chaneg <h3><% trip.get('name'); %></h3> by <h3><%= trip.get('name') %></h3>
My code seems to be right but still my template was getting compiled on page load and I was getting trip is not defined error. I did not understand the reason of this behavior yet.
I solved this issue by using handlebarsjs instead of default underscore templates.
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).
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.