It's hard to me to explain what I'm looking for, so I will start with code.
I have Marionette view like this:
Marionette.ItemView.extend({
model: new Models.Cards(),
template: 'poker/cards',
events: {
'click player': 'playerClicked'
},
playerClicked: function( e ) {
// THIS WORKS!
}
}
How can I do something like this:
events: {
'click player': 'playerClicked',
'render player': 'playerRendered'
},
so that playerRendered be called when <player> is rendered?
If you want to run some code when the ItemView itself is rendered, use onRender:
Marionette.ItemView.extend({
// ...
onRender: function() {
console.log("Rendered the ItemView!")
}
//...
})
Marionette doesn't have built-in events for parts of an ItemView being rendered.
#joews is right. If you want to react to a specific piece of your ItemView being rendered you should react on the render event for the whole ItemView. When you render an ItemView it renders the whole template in memory and then appends it to the DOM as one piece. If you want to render a collection of models then you can use a collection view and each ItemView will be rendered independently.
If you want an example of setting up views with CollectionViews and CompositeViews check out this fiddle of rendering four hands of five cards, all the cards having random values: http://jsfiddle.net/kjdgygy5/
Related
I am using backbone.js to create a page. My code contains many models and views. I wonder if it is possible to destroy a view and then redraw it without refreshing the page, and if so, what is the best way to do it.
$(document).ready(function() {
var myHomeCollectionView = new MyHomeCollectionView({});
});
var MyHomeCollection = Backbone.Collection.extend({
model: MyHome
});
var MyHomeCollectionView = Backbone.View.extend({
el: "#home",
initialize: function(options){
_.bindAll(this, 'render');
this.collection = new MyHomeCollection();
/-- Rest initialize the code --/
},
render: function(){
/-- Render code --/
}
})
this is a sample code of my view..
Yes. It is certainly possible. The main benefit of a JS framework is being able to change the content of the page without refreshing it.
I am not sure why you want to destroy the view, that is usually not necessary.
If you simply want to re-render the same view, you usually just listen for an event then call render. Take a look at the example below of re-rendering your view based on when the collection reloaded.
var MyHomeCollectionView = Backbone.View.extend({
el: "#home",
initialize: function(options){
_.bindAll(this, 'render');
this.collection = new MyHomeCollection();
// re-render view when collection is reloaded
this.listenTo(this.collection, 'reset sync', this.render);
/-- Rest initialize the code --/
},
render: function(){
/-- Render code --/
}
})
Or you can replace a view with another view. You can do this by simply rendering another view into the same element. Check out this jsfiddle for a very simple example of this: http://jsfiddle.net/1g1j7afa/2/.
If you want to get more advanced, you can check out Marionette LayoutView. It is a nice way to handle the adding/replacing of sub views.
Consider the following JS:
var ChildLayout = Marionette.Layout.extend({
template: "... let's not go here ...",
initialize: function() {
console.log('Child Layout Initialized'); // registers once
},
onRender: function() {
console.log('Rendered'); // registers 2 times
},
});
var ParentLayout = Marionette.Layout.extend({
template: "<div class='child-region'></div>",
regions: { childRegion: '.child-region' },
onRender: function() {
console.log('About to initialize ChildLayout'); // registers once
this.childRegion.show(new ChildLayout());
},
});
In the above, I use the ParentLayout to render the ChildLayout in one of its Regions. Notice that I do not pass any sort of model to the ChildLayout.
The show function, a property of Marionette, should logically initialize and then render the model once. A Marionette View should not re-render itself unless there is some change in its model, from what I understand.
In my application, the onRender of ChildLayout is triggering in my code several times, though its initialize only triggers once.
I cannot see what is causing Marionette to render the ChildLayout multiple times - this does not make sense.
Any insight?
edit
On inspecting the source code of Marionette.js, the show function clearly only renders the passed view once, right after initializing. So the re-renders could only occur from the Layout deciding autonomously to re-render. Interesting.
This turned out to be my own problem with a Zombie View, which I since handled.
What approach would be more efficient?
I have a Backbone.Collection so i Create a Backbone.View to render this collection. The CollectionView render method:
render: ->
container = document.createDocumentFragment()
#collection.each (item) ->
view = new ItemView(item)
container.appendChild view.el
view.render()
$(el).append container
I can use the events in two forms.
1.- Set the events object in CollectionView, so i need to declare the action of select an item in the CollectionView and "rescue" the model that i selected.
CollectionView extends Backbone.View
events:
'click #itemView', 'onSelectItem'
onSelectItem: ->
##Get the model
##Show ItemDetailView
2.- Set the events object per itemView, so the select method don't need to retrieve the model.
ItemView extends Backbone.View
events:
'click #div','onSelect'
onSelect: ->
#Show ItemDetailView
Which of this options are better?
EDITED: I create a JSperf snippet http://jsperf.com/backbone-events-on-collectionview-or-per-itemview
JSperf show us that the ItemView approach is more faster, but is this the only metric of importance?
The second approach is much better. Performance aside, the code is much more straightforward. In 6 months, if you had to return to this code, would you think the event would be handled in the ItemView, or the CollectionView? It is a click event on the ItemView, so that's where I would go to look for how it is handled.
Is there a good reason to handle the event in the collection? If you needed the handle the event from the CollectionView, you could delegate the event to the collection. A little redirection, but, to me, this is far clearer. For example:
# The collection view
initialize: ->
#listenTo #, 'selected', #itemSelected
render: ->
container = document.createDocumentFragment()
#collection.each (item) ->
view = new ItemView(item, parent: #)
container.appendChild view.el
view.render()
$(el).append container
itemSelected: (model)->
# Do whatever you need to here, like
# show the ItemDetailView in the container
# The ItemView
ItemView extends Backbone.View
events:
'click #div','onSelect'
onSelect: ->
#options.parent.trigger('selected', #model, #)
#Show ItemDetailView
If I understand you correctly, option 2 seems the most sane. You will be referring to this.model as opposed to digging through the collection to find the model associated with the clicked view.
Simple view (reduced code):
My.View = Backbone.View.extend({
className: 'my-view',
initialize: function() { },
render: function() {
console.log('render');
return this;
}
});
I use it in another view as subview like this:
var myView = new My.View();
this.$el.append(myView.render().$el);
First instead of a className the view had a template and the view was rendering fine.
However when I removed the template and added className instead it is not rendering correctly.
It just renders the div and the correct class name however no logging in the render method is performed. AND when I add some html inside the render method to this.$el it never appears. Any ideas why?
UPDATE:
When I put my custom rendering code inside the afterRender method it works. Why is it not possible to overwrite the render method in my case?
this.$el.append(myView.render().el);
I would render the subview the following way.
var myView = new My.View();
this.$el.append(myView.el);
myView.render();
I have a model relationship set-up with Backbone Relational, whereby an Item belongs to a Column. Adding items to columns works well, but how do I reference an existing view for an item, in-order to remove it from its old column? (Therefore, items are currently duplicating across columns.)
Please see this JSFiddle - http://jsfiddle.net/geVPp/1 (I presume code will need to be implemented in the ColumnView removeItem event handler, but I may be wrong).
(The example instantiation is at the bottom of the script, as I have no UI controls yet.)
Here is the ColumnView from the fiddle:
var ColumnView = Backbone.View.extend({
className: 'column',
tagName: 'div',
template: Handlebars.compile($('#column-template').html()),
initialize: function() {
_.bindAll(this, 'render', 'renderItem', 'removeItem');
this.model.bind('change', this.render);
this.model.bind('reset', this.render);
this.model.bind('add:items', this.renderItem);
this.model.bind('remove:items', this.removeItem);
},
render: function() {
return $(this.el).html(this.template(this.model.toJSON()));
},
renderItem: function(item) {
var itemView = new ItemView({model: item});
this.$('.items').append($(itemView.render()));
},
removeItem: function(item) {
// #todo -- How do I reference the item model's view, in order to remove the DOM element?
}
});
Unfortunately Backbone has no built-in way; you have to keep track of your child views (and which model they map to) manually.
Backbone.Marionette has a CollectionView class that can automate this for you. Or you could roll your own similar to UpdatingCollectionView.
If you want a quick fix, you could keep a reference to the view as a variable of the model object, but that's bad because it prevents you from showing multiple views of the same model.