A simple and short question: If a view contains two or more sub views. Should the view container be a layout view?
If not, what are good alternatives?
Update:
my code:
var LikeButtonModal = Backbone.Model.extend({
url: 'api/profile/like/'
});
var LikeButton = Backbone.Marionette.ItemView.extend({
tagName: 'button',
className: 'like',
template: '<div>like</div>',
events: {
'click' : 'like'
},
initialize: function(userId){
this.model = new LikeButtonModal();
},
like: function(){
this.model.save();
}
})
var LeftProfileView = Backbone.Marionette.Layout.extend({
template: '#profile-left',
regions:{
extra : '.extra'
},
initialize: function(){
this.on("item:rendered", this.editable, this);
},
onRender: function(){
if(this.model.get('userid') != ActiveUser.get('userid')){
this.extra.show(new LikeButton(this.model.get('userid')));
}
}
});
Layouts are good for this if you will be replacing the sub-views at different times, or if the sub-views are very different types... for example, a layout might contain your header, your navigation and your main content region.
Other options are CollectionViews and CompositeViews.
Collection views will render a collection of items, using the same type of view for each item in your collection. This works well for lists of things.
CompositeViews are CollectionViews that can render a wrapper template around the collection. For example, an HTML table structure. The table, thead, tbody and tfooter tags can be rendered in the CompositeView's wrapper template, and then a collection of items can be rendered in to the tbody tag.
This might shed a little more light on the subject, too: https://github.com/derickbailey/backbone.marionette/wiki/Use-cases-for-the-different-views
Related
Consider the following Marionette composite View. Does anyone know wy appendHtml event does Not fire:
var TreeView = Backbone.Marionette.CompositeView.extend({
template: "#node-template",
tagName: "ul",
initialize: function(){
this.collection = this.model.nodes;
},
appendHtml: function(collectionView, itemView){
alert('appendHtml triggered');
collectionView.$("li:first").append(itemView.el);
}
});
look at the alert('appendHtml triggered'); Why does it not fire?
Has that been removed?
Look at the docs. Depending on the version of Marionette you are using. Use attachHtml()
I am trying to understand how to use a plugin like http://johnpolacek.github.io/superscrollorama/, with Backbone.js by integrating it into my Views. I know that I need to hook into the backbone View-Events, but I want to do a horizontal scroll with the plugin, and I don't know of a horizontal scroll-event. How can I still utilize the plugin? Thanks in advance for any ideas.
Views:
var ArtistsView = Backbone.View.extend({
tagName: 'ul',
initialize: function () {
this.cleanUp();
$("body").attr('id','artists');
this.render();
},
events: {
"click div.open" : "largeArtViewOpen",
"click div.close" : "largeArtViewClose",
},
render: function () {
this.collection.each(function(model) {
var artistView = new ArtistView({ model: model });
this.$el.append(artistView.render().el);
}, this);
console.log('and a new view was rendered!')
return this;
},
cleanUp: function(){
if (this != null) {
this.remove();
this.unbind();
console.log('View was removed!');
}
},
largeArtViewOpen: function(e){
var thisArt = $(e.currentTarget).parent().attr("class");
console.log(thisArt);
$("#open-view, li."+thisArt).show();
},
largeArtViewClose: function(e){
//var thisArt = $(e.currentTarget).parent().attr("class");
console.log('clicked!');
$("#open-view, ul#large li").hide();
},
scrollFx: function(){
var controller = $.superscrollorama({
isVertical:false
});
controller.addTween('h2#fade-it', TweenMax.from( $('h2#fade-it'), .5, {css:{opacity: 0}}), 800);
//$('h2#fade-it').css({'color':'#dbdbdb'});
console.log('scroll message!');
},
});
var ArtistView = Backbone.View.extend({
tagName:'li',
className:'artistLink not-active',
render: function(){
this.id = this.model.get('idWord')+"-menu-item";
this.$el.attr('id', this.id).html(this.template(this.model.toJSON()));
return this;
},
});
So, in the past 3 days since I've asked this question, I've spent some time trying different scrollable 'targets' for Superscrollorama...Document vs. Window vs. Body vs. other DOM elements within the HTML, and the questions that I've had to consider are, should the scroll event be bound to the View's top element? Should it be bound to the body, but initialized in the view? In both cases I tried, I couldn't get the scroll events to continuously fire...this may just be due to bad code, but I couldn't make it happen.
So, what I arrived at, was, avoiding the view entirely: I instantiating and called Superscrollorama in a function called scrollFx() within a separate 'helper.js' document, and then called scrollFx() from my view's router.
I'm thinking I will just empty the target's styles and unbind any existing scroll events in the beginning of scrollFx(), before I call the Superscrollorama function so that the resulting scroll styles/animations are cleaned up, and events aren't exponentially bound.
I'm still very much working through these issues, though now the scroll events are working, so if anyone happens to read through this train of thought, please feel free to add your two sense, especially, if you have better ideas about re-implementing the Superscrollorama function within the View itself.
Thanks.
I searched info on this topic but found only info about getting event element.
Yes, I can get an element of clicked div, but why it's fired all 19 times? (it's the number of views total). Info of clicked event is same - of the clicked div.
Here is what divs look like: http://d.pr/i/AbJP
Here is console.log: http://d.pr/i/zncs
Here is the code of index.js
$(function () {
var Table = new Backbone.Collection;
var TrModel = Backbone.Model.extend({
defaults: {
id: '0',
name: 'defaultName'
},
initialize: function () {
this.view = new Tr({model: this, collection: Table});
this.on('destroy', function () {
this.view.remove();
})
}
});
var Tr = Backbone.View.extend({
el: $('.pop-tags').find('.container'),
template: _.template($('#td_template').html()),
events: {
'click .tag': 'clicked'
},
clicked: function (event) {
event.preventDefault();
event.stopPropagation();
console.log(event.currentTarget);
},
initialize: function () {
this.render();
},
render: function () {
this.$el.append(this.template(this.model.attributes));
return this;
}
});
for (var i = 0, size = 19; i < size; i++) {
var trModel = new TrModel({id: i, name: 'name_' + i});
Table.add(trModel);
}
});
How can I avoid all elements from firing an event and fire only one that clicked and only 1 time?
el: $('.pop-tags').find('.container'),
Don't do that. You are attaching every view instance to the same DOM node. Each backbone view needs a distinct DOM node or, as you see, delegate events become complete chaos. In your view, set tagName: 'tr', then when creating your views, create them, call .render() and then append them to the DOM with something like $('.table-where-views-go').append(trView.el);.
You also may want to brush up on the basic MVC concept because Tables and Rows are view-related notions, not model-related, so a class called TrModel is a code smell that you aren't clear on Model vs View.
I would use a slightly different approach to solve your problem.
Instead for one view for every tr I would create one view for the entire table.
When I create the view I would pass the collection containing the 19 models to the view and in view.initialize use the collection to render the rows.
I created a jsbin with a working example.
I'm wondering if/how a CompositeView can pass data down into its defined itemView. Example (reduced) code:
var TableView = Backbone.Marionette.CompositeView.extend({
template: '#table-template',
itemView: TableRowView,
itemViewContainer: 'tbody',
});
var TableRowView = Backbone.Marionette.ItemView.extend({
tagName: 'tr',
template: '#table-row-template',
serializeData: function () {
var data = {
model: this.model,
// FIXME This should really only be called once. Pass into TableView, and down into TableRowView?
// That way, getDisplayColumns can be moved to the collection as well, where it makes more sense for it to belong.
columns: this.model.getDisplayColumns()
};
return data;
}
});
I'm using the two to render a html table. #table-row-template has some render logic for supporting different types of "columns". This allows me to use the same views for different types of Collections/Models (as long as they follow the API). So far, it's working pretty well!
However, as you can see above, each "row" makes a call to get the same "columns" data every time, when really I just wanted to pass that on down once, and use for all.
Recommendations?
Thanks!
You can use itemViewOptions either as an object or a function
var TableView = Backbone.Marionette.CompositeView.extend({
template: '#table-template',
itemView: TableRowView,
itemViewContainer: 'tbody',
itemViewOptions: {
columns: SOMEOBJECTORVALUE
}
});
OR
var TableView = Backbone.Marionette.CompositeView.extend({
template: '#table-template',
itemView: TableRowView,
itemViewContainer: 'tbody',
itemViewOptions: function(model,index){
return{
columns: SOMEOBJECTORVALUE
}
}
});
and then receive the options with:
var TableRowView = Backbone.Marionette.ItemView.extend({
tagName: 'tr',
template: '#table-row-template',
initialize: function(options){
this.columns = options.columns;
}
});
(* Note that itemView, itemViewContainer and itemViewOptions are changed in version 2 to childView , childViewContainer and childViewOptions).
Does it make sense to let a backbone.js-view know about it's parent element, when you have a simple view containing very little logic, or is it bad practice?
Like this:
var BooklistRow = Backbone.View.extend({
tagName: "li",
parent: "#booklist",
render: function() {
$(this.el).html("<b>" + this.model.get("title") + "</b>");
$(this.parent).append(this.el);
return this;
}
});
It is better if a view knows nothing outside of itself. This will make it more reusable.
Also in your example, you are adding to the parent on render. At some point, you may want to re-render the BooklistRow after it is already appended to the parent.
I think it is better for the parent to render and add the children:
var Booklist = Backbone.View.extend({
tagName: "ul",
render: function() {
// maybe should remove existing books here first
this.model.each(this.addOneBook);
return this;
},
addOneBook: function(book) {
var view = new BooklistRow({
model: book
});
$(this.el).append(view.render().el);}});
Now, if a single book changes, it can re-render itself without the list even knowing.