Marionette CollectionView with one unique ItemView - backbone.js

Is there a way I can create a single unique view within a Marionette CollectionView? I mean something like this:
CollectionView
StartView (this is the unique view)
Regular ItemView
Regular ItemView
Regular ItemView
etc.
By unique, I mean the "Start ItemView" must use a unique template, and not have the methods associated with the regular ItemViews.
Things I've tried:
Just prepending the start view to the el of the CollectionView is not desirable, because my CollectionView's methods act on its child views.
In the Marionette documentation, overriding buildItemView is suggested for this situation (I think?). I'm not sure how to override it. Just calling buildItemView doesnt work, because the StartView doesn't have an associated model or any options. Seems like I can't leave these args out, or it just returns undefined.
I cant include the markup for a StartView in my CollectionView's template because... Marionette CollectionViews cant have a template.
From the Marionette documentation, it doesn't seem like this fits the use case for the CompositeView. But maybe I'm wrong.
I'm probably missing something fairly obvious. Thanks in advance for the help.
edit: formatting

Create 2 different views, 'StartView' and 'NormalView':
StartView = Marionette.ItemView.extend({
template:"#start-template'
});
NormalView = Marionette.ItemView.extend({
template:"#normal-template"
});
In your collection view, override getItemView and return your 'StartView' and 'NormalView'... something like this:
MyCollectionView = Marionette.CollectionView.extend({
getItemView:function(item){
if(item.get("ImTheFirst")===true)
return StartView;
else
return NormalView;
}
});
On the first item in your collection, add a property 'ImTheFirst' (for example) and set it to true.

What you are looking for here is a CompositeView. A CompositeView is a mash-up between an ItemView and a CollectionView. Essentially, the CompositeView itself would act as your StartView, and then you define an ItemView to act as your "Regular ItemView"'s.
Something like this:
RegularItemView = Backbone.Marionette.ItemView.extend({
template: "#regular-itemview-template"
});
CompositeView = Backbone.Marionette.CompositeView.extend({
itemView: RegularItemView,
template: "#start-template"
});

Related

Prevent itemView from being added to CompositeView after collection "add"

I have a problem with Backbone and Marionette. I have a CompositeView with a collection where people can a comment, this all works nicely, the comment is added and saved to the server but I don't want the view to update and to show the newly added comment. I have tried this:
App.Views.CommentsView = Backbone.Marionette.CompositeView.extend({
template: '#article-comment-container',
itemViewContainer: 'ul',
itemView: App.Views.CommentView,
collectionEvents: {
"add": "modelAdded"
},
modelAdded: function(){
console.log('Please do nothing!');
}
});
But the item is still rendered into the page on top of my modelAdded function being called. Can I prevent that from happening at some point?
In a different scenario I would like new items to be added to the top of the list and not the bottom. Do I have to override the entire appendHtml method achieve this?
Setting the collection event add simply adds another handler to the queue for that event; it doesn't replace any other events so the default marionette behaviour will still occur.
I assume you're calling the create method on the collection to create your new comment model. If this is the case you simply need to set the silent option to true. Now the add event will not fire and Marionette will not create and render the view for that model. You can do it like this:
commentCollection.create(commentModel, {silent: true});
As for you second question about prepending, yes I would override appendHtml method. Or to keep the method names consistent with what actually happens, create a method called prependHtml and then override the renderItemView method to call prependHtml.

View event reflected in collection convention

What is the best way to remove a model from a collection that has been removed in the DOM. Let me ask a better question, how do I keep views in sync with a collection?
remove the view first, while removing execute
this.model.collection.remove(this.model);
you can check with conditions to whether current view has a model, and that model has a collection etc before you execute the same.
I've followed the backbone Todos example application. This keeps view state up to date with collection.
Pass models to any view created like so:
var someView = new SomeItemView({ model: modelFromCollection });
Then listen to events on that model and react from the view:
initialize: function() {
this.listenTo(this.model, 'destroy', this.remove);
// listen to other events ...
}

Get last item view from CompositeView in Marionette

Is it possible to get the last ItemView from a Backbone CompositeView? I've found a lot of documentation for getting the last model in a Collection, but not that last View in a Collection of Views.
The reason I would like to do this is so I can render the last row in a table slightly differently.
The following is the code I'm using right now, it works fine, but it would be less "hacky" if I could get the correct ItemView from the CompositeView that created and rendered it. It uses jQuery to search the entire part of the DOM contained by the CompositeView for the last element, then manipulates that element.
B.ListControl.View = Backbone.Marionette.CompositeView.extend({
itemView: ...,
itemViewContainer: ...,
template: ...,
// ON RENDER
onRender: function(){
// Add button to the last element in the list
this.$el.find('div:last').text('Custome stuff goes here');
}
});
Thanks!
When your collection is fetched you can get last item in this way:
this.children.findByIndex(this.children.length - 1);
Babysitter plugin provides a lot of useful methods for you:
findByModel, findByCollection, findByCustom, findByIndex, findByCid

Get rid of surrounding elements in Backbone.Marionette

I have the following view:
return Marionette.ItemView.extend({
el: '<section>',
template: JST['app/scripts/templates/grid.ejs'],
that is called like this:
// a Layout
regions: {
grid: '#grid',
detail: '#detail'
},
onShow: function () {
var detailModel = new DetailModel();
var g = new GridView(detailModel);
this.grid.show(g);
}
The question is: How do I get rid of the surrounding section element ? I tried to omit the el property but that gives me the following strange looking div:
<div productname>
Regards Roger
The surrounding element is required for backbone to work. It is essentially a container/placeholder for your view to sit in, whether its contents have been rendered or not.
If you really insist on not having the container then I would consider resorting to the following:
https://github.com/marionettejs/backbone.marionette/blob/master/docs/marionette.region.md#set-how-views-el-is-attached
Marionette.Region.prototype.open = function(view){
this.$el.empty().append(view.$el.children());
}
I say 'resorting' because, in my opinion, this is not how Backbone is supposed to be used and may have side-effects. (im not quite sure what will happen when the view in that region tries to re render; what will it's el element be pointing to?)
To expand on Scott's answer, it's probably a very bad idea to try and force the removal of the surronding view tags.
All Backbone views are contained within an DOM element. Given this fact, you have 2 main options:
have Backbone put your view into the default div element
specify which element you want Backbone to wrap your view with, using the el or tagName attributes
If the "extra" tags are creating issues (e.g. you need to generate a specific HTML set for use with a plugin), then you're not defining the wrapping element properly. For more on the subject, take a look at this blog post of mine: http://davidsulc.com/blog/2013/02/03/tutorial-nested-views-using-backbone-marionettes-compositeview/
Edit based on jsFiddle: the reason for your strange behavior is that you were passing a model instance to the initialize function. This is then interpreted as attributes for the view and get set as HTML attributes.
The correct way to provide a model instance to a view is :
new App.FooterView({
model: new App.Model()
})
In other words, you provide a javascript object to the view, with a model property. If you want to learn Marionette basics quickly, check out the free preview to my book: http://samples.leanpub.com/marionette-gentle-introduction-sample.pdf (You'll find how to instantiate a view with a model on pages 15-21)

Binding Backbone change event to attribute of model in a collection

I have a view that takes a collection like below:
MyView = Backbone.View.extend({
initialize: function() {
this.collection.on("change://an attribute of a model in aCollectionToRender", someAction);
}
});
var MyViewInstance = new MyView({
collection: aCollectionToRender
});
Basically, I don't want MyView to listen for changes on the entire collection, only a single attribute of the model that the collection contains.
I realize there are a number of alternatives to this:
Create views for each model of aCollectionToRender and attach .on("change") events in those views. I don't want to do this because it's not the right thing to do for my situation
Just have a this.collection.on("change") event and have the event handler filter based on my requirements. This is less elegant, and if Backbone already allows me to write event filters as I asked above, this is duplicate code
I was just wondering if there's any way to write an event listener that already does the filtering. This question may also be a duplicate; I looked but I didn't find anything exactly like this, however, there are a lot of Backbone questions
Maybe I am misunderstanding your question, but you can do exactly what you show. Check out the Backbone documentation.
MyView = Backbone.View.extend({
initialize: function() {
this.collection.on("change:attributeName", this.someAction /*function to call*/, this);
},
someAction: function(model, value, options){
}
});

Resources