trying to wrap my head around backbone.js and so far I like what I'm seeing. I have a question about views that I'm not quite understanding. I'm creating a demo app that uses a form input to append a LI to a UL. Pretend it's a list of movies. So I have:
AppView, the view for my entire application
MovieView, a single LI in the UL of movies
When you click to add a movie, I want to create a new MovieView and append it to the UL in my AppView. I've got a fiddle here: http://jsfiddle.net/nCEz2/
What's happening is that instead of appending an LI to the UL, each time I create a new MovieView, the old MovieView is updated. So instead of appending a new LI to the UL each time, I only ever have the first one I added, updated to the newest item I've added. Any ideas why I'm only getting one MovieView and it's not appending like I think it should?
Your example isn't working the way you expect because the $el property of MovieView is initialised once so it always represents the same DOM object in all instances of MovieView. So although you change the text of the li each time you render a new MovieView you are always adding the same li to the ul and after the first time you add it thats a no-op.
One way to get the behaviour you are expecting is to use the tagName property so that backbone will automatically create a new li element for each view instance. e.g.
var MovieView = Backbone.View.extend({
tagName: 'li',
initialize: function () {
_.bindAll(this, 'render');
return this.render();
},
render: function () {
$(this.el).text(this.model.get('name'));
}
});
Then in you AppView.movieAdded method:
movieAdded: function (newMovie) {
var movieView = new MovieView({
model: newMovie
});
$('#movieList').append(movieView.el);
},
Related
Say I have these two Backbone.Marionette views:
var FooView = Backbone.Marionette.ItemView.extend({
tagName: p,
id: 'foo',
template: this.templates.summary
});
var BarView = Backbone.Marionette.ItemView.extend({
template: this.templates.summary
});
And then I want to show them inside an app region, like so:
App.contentRegion.show(new FooView/BarView());
The first view would create a new element and append it to the region. I thought the second way would be more like a standard Backbone view and attach itself to the region without creating a new element, but it wraps it in a tag. Is there a way to avoid this without using something like setElement()?
For this, you should use the attachView method: https://github.com/marionettejs/backbone.marionette/blob/master/docs/marionette.region.md#call-attachview-on-region
I am rendering a view for each item in a collection, each of those items contain a list that I am trying to append to an ul but they are being appended to the ul of other views.
View:
initialize: function(){
},
render: function(){
var current = this.model.toJSON();
this.$el.html(_.template(cTemplate, current));
var items = this.model.toJSON().items;
items.forEach(function(item){
this.$('.list').append('<li>'+item.attributes.price+'</li>');
});
return this;
}
Template:
<ul class="list"></ul>
I am trying to append to this.$('.list') which I thought would mean I am appending the price of each item only to the class of my template in the current view. It appears I cannot reference it like this because the prices are appended to all list classes in views rendered before the current one.
I am wondering how I prevent appending to all of the same classes on the same page and just to the one from the template of my current view.
If ul is the tagName of this view class, you can simply append to this.
items.forEach(function(item){
this.$el.append('<li>'+item.attributes.price+'</li>');
});
or, ul.list is a child element, you can do this
items.forEach(function(item){
this.$el.find('ul.list').append('<li>'+item.attributes.price+'</li>');
});
Note: This pattern may not be maintainable in future when complexity increases. You can considering creating individual view for each item if those items has interactions.
Why is the el undefined on a page refresh or page load? Is it because the el does not exict?
class ListView extends Backbone.View
className: 'channels-list'
el: '#main'
initialize: ->
console.log #el
# undefined
The element needs to be a tag name or element that already exists in the DOM. When a new view is created the _ensureElement function is called:
// Ensure that the View has a DOM element to render into.
// If `this.el` is a string, pass it through `$()`, take the first
// matching element, and re-assign it to `el`. Otherwise, create
// an element from the `id`, `className` and `tagName` properties.
_ensureElement: function() {
//...
}
To avoid this place the script tag below the element tag or wait for the document load event, for example using jQuery:
jQuery ->
class ListView extends Backbone.View
className: 'channels-list'
el: '#main'
initialize: ->
console.log #el
Or, as Vitaliy suggested, rest .el in the initialize function.
Exactly. If you define el it should be available in DOM structure
Inside initialize method you can set element again if it already exists in DOM:
#setElement $('#main')
I've been porting my app to use jqMobi and jqUI, but I've run into a problem with backbone delegating events.
The way jqUI creates a side nav bar is umm.... interesting to say the least.
Each panel can have a distinct nav bar, but the nav bar is never actually visible to the user, you populate the nav bar, and then jqUI copies the html into the div#menu element.
My view is fairly straightforward
MyApp.Views.UserMenu = Backbone.View.extend({
el: 'nav#user_menu',
initialize: function(){
//empty out and unbind in-case it is already populated
$(this.el).empty().unbind();
this.render();
},
events: {
"click div#add_friend": "new_friend"
},
render: function(){
$(this.el).append(HandlebarsTemplates['friends/new_friend']());
// here I am trying to change the 'el' to point to where the menu is in the DOM
this.el = 'div#menu';
this.delegateEvents();
return this;
},
new_friend: function(){
alert('clicked');
}
});
I've tried changing the el to the div#menu after populating the nav, but that isn't working. I've also tried populating the div#menu directly, but that doesn't seem to work either.
Any suggestions? I'm assuming the issue is that the elements are being moved, but it could be something else, and maybe I'm not sure how to debug the other case.
I have model Post and collection Posts. And want to make form with list of all post in <select id="multi" multiple="multiple">. So i have to make a PostView render inside my #multi with just this template:
<option value=""><%= title %></option>
But finally I get it wrapped with div. Is there any solution for not wrapping this template with <div>?
If you don't define an el (or tagName) for the view (in the class or during instantiation) the view will be placed inside a div tag. http://documentcloud.github.com/backbone/#View-el
var PostView = Backbone.View.extend({
tagName: 'option'
});
UPDATE
Starting v0.9.0, Backbone has view.setElement(element) to get this done.
var PostView = Backbone.View.extend({
initialize: function() {
var template = _.template('<option value=""><%= title %></option>');
var html = template({title: 'post'});
this.setElement(html);
}
});
If you don't want to have the view wrap your HTML, you'll have to do a few things:
Replace this.el entirely
Call delegateEvents on the new el
render: function(){
var html = "some foo";
this.el = html;
this.delegateEvents(this.events);
}
Since Backbone generates a div or other tag (based on your tagName setting for the view), you have to replace it entirely. That's easy to do. When you do that, though, you lose your declared events because Backbone uses jQuery's delegate under the hood to wire them up. To re-enable your declared events, call delegateEvents and pass in your events declarations.
The result is that your view.el will be the <option> tag that you want, and nothing more.
In version 0.9.0, Backbone introduced view.setElement(element) to handle this operation.