Is it conventional to use Marionette.ItemView for view classes that do not have a specific model property associated with them?
As Marionette.View is not meant to be used directly, it seems like an ItemView makes sense as a view class with convenient defaults and bindings.
Or, should one just resort to using Backbone.View? If so, is there a way to hook Backbone.View into Marionette's evented and garbage-collected architecture?
Thank you for clarification!
ItemView can be used without a model. I do this quite regularly.
If you need to specify data for an ItemView, but not have that data in a Backbone.Model, you need to override the serializeData method:
MyView = Marionette.ItemView.extend({
serializeData: function(){
return {
my: "custom data"
};
}
});
the base Marionette.View isnt' meant to be used directly because it doesn't provide a render function on it's own. That doesn't mean you can't use it to create your own base view types, though. You could, for example, build a view type for your application that deals with rendering google maps or a third party widget or something else that doesn't need the general Backbone.Model based rendering that ItemView has in it.
I just found out you can use a templateHelper for this - just chuck this in your ItemView declaration:
templateHelpers: function() {
return {
message: this.message,
cssClass: this.cssClass
}
}
And then in your template:
<script type="text/html" id="notice-template">
<span class="<%= cssClass %>"><%= message %></span>
</script>
And then when you initialise the view:
var noticeView = new App.Views.Notice();
noticeView.message = "HELLO";
App.noticeRegion.show(noticeView);
I would be interested in your thoughts on this Derick?
Related
Trying to have a data field in a Handlebars template update after the model that is assigned to the Marionette CompositeView is fetched, but the HTML in the page is not getting updated.
My code looks like this:
Model:
B.Page.Model = Backbone.Model.extend({
url: function () {
return 'my/resource/';
},
});
View:
B.Page.CompositeView = Backbone.Marionette.CompositeView.extend({
template: Handlebars.compile(templates.find('#my-template').html()),
initialize: function(options) {
_.bindAll(this);
this.model.fetch();
},
)};
Template:
<script id="my-template" type="text/x-handlebars-template">
Date: <span id="my-data-field">{{data}}</span>
</script>
I have checked the resource and it does return proper JSON with the data field set. Also, the model is getting passed in to the view.
I suspect that this is due to the render function not getting called after the data is retrieved; however, I would like to get feedback on how it should be done.
What is a good way to do this?
Thanks!
EDIT: This CompositeView does have a Collection that is associated with it (which renders just fine when I trigger the appropriate event). I purposefully left out that part of the code to avoid muddying up the problem.
Your are right, since a CompositeView extends from CollectionView, it only re-renders on collection events by default. To make it re-render on changes on your model, you could do something like this in your CompositeView:
initialize: function(){
this.listenTo(this.model, "change", this.render);
}
All Marionette views have a modelEvents object that is bound to the passed in model. So you could clean the accepted answer up slightly by doing:
template: Handlebars.compile(templates.find('#my-template').html()),
modelEvents: {
'change': 'render'
}
rather than binding manually in initialize.
I'm using a Marionette CompositeView to render an html table. Works great! Now I want to display a message when there are no records in the collection. I'm currently using the emptyView property to render this message. However, the message is rendered in the table wrapper and the tables column headers are still visible. Not exactly what I want. Ideally, I would like to hide/remove the table and display the empty records view and then show it when records are added. I'm struggling to find to best approach to handling this. Are there any suggestions out there?
EmptyView = Marionette.ItemView.extend({
template: "#empty-template"
});
SupportMemberView = Marionette.ItemView.extend({
template: "#member-template"
});
SupportTeamView = Marionette.CompositeView.extend({
template: "#support-team-template",
itemView: SupportMemberView,
emptyView: EmptyView,
itemViewContainer: 'tbody'
});
The accepted answer imposes a dependency between the empty view and the template, which does not feel right.
I think an alternative way to do this is to use dynamic templates in the composite view. This is done by overriding the base View getTemplate() method. Thus your composite view would be defined as follows, assuming you have access to the underscore.js library or equivalent to replace the "_.isEmpty()" function:
SupportTeamView = Marionette.CompositeView.extend({
getTemplate: function() {
if (_.isEmpty(this.collection)) {
return "#empty-template"
} else {
return "#support-team-template";
}
itemView: SupportMemberView,
emptyView: EmptyView,
itemViewContainer: 'tbody'
});
One thing that you can do is on your emprty View use the onRender function to hide the table. this function is called after the render function, so you will be able to manipulate the dom to look the way you want.
I have a CompositeView with a list of ItemViews. The same collection is rendered for both states, when a user is logged in, and when a user is logged out.
The ItemView looks roughly like:
<div class="title">
{{ title }}
</div>
{{#if MA.currentUser }}
Add Review
{{/if}}
With the JavaScript:
MA.Views.Items.Movie = Backbone.Marionette.ItemView.extend({
template: 'items/movie',
className: 'movie'
});
However, this does not seem to display the expected 'Add Review'.
Any suggestions what could be done in this situation?
My understanding is that the template will only have access to the data that is passed to it. By default this is the model which is serialized by serializeData in the View Class which ItemView inherits. You can add additional data by using templateHelpers or writing a custom serializeData(may be other ways also).
Template Helpers
template helpers in a ItemView could be like:
templateHelpers: {
MA: function(){
return MA; //Unknown if having the same function name as the global will effect it
}
}
In your item view. See marionette docs template helper
Template helpers can be a function, object literal or object.
serializeData
For serializeData you just overwrite the serializeData method in your ItemView's Class.
serializeData: function(){
return {
"some attribute": "some value"
}
Worth noting that this would require you to serialize your model also. marionette docs serializeData
Conclusion
templateHelpers may turn out to be the simplest answer and probably what they were intended for.
I do an extensive use of templates, and I like to use full contained templates. I mean that I want to see in the template code all the DOM elements including the root one, like this:
<script type="text/template" id="template-card">
<div class="card box" id="card-<%= id %>">
<h2><%= title %></h2>
<div><%= name %></div>
</div>
</script>
But what Backbone likes is having a template like this:
<script type="text/template" id="template-card">
<h2><%= title %></h2>
<div><%= name %></div>
</script>
And defining the root element and its attributes in the JS code. What I think is ugly and confusing.
So, any good way to avoiding my Backbone View to wrapper my template with an extra DOM element?
I have been checking this issue thread: https://github.com/documentcloud/backbone/issues/546 and I understand there is not any official way to do it.. but maybe you can recommend me a non official way.
You can take advantage of view.setElement to render a complete template and use it as the view element.
setElement view.setElement(element)
If you'd like to apply a Backbone view to a different DOM element, use setElement, which will
also create the cached $el reference and move the view's delegated
events from the old element to the new one
Two points you have to account for:
setElement calls undelegateEvents, taking care of the view events, but be careful to remove all other events you might have set yourself.
setElement doesn't inject the element into the DOM, you have to handle that yourself.
That said, your view could look like this
var FullTemplateView = Backbone.View.extend({
render: function () {
var html, $oldel = this.$el, $newel;
html = /**however you build your html : by a template, hardcoded, ... **/;
$newel = $(html);
// rebind and replace the element in the view
this.setElement($newel);
// reinject the element in the DOM
$oldel.replaceWith($newel);
return this;
}
});
And a working example to play with http://jsfiddle.net/gNBLV/7/
Now you can also define a view's tagName as a function and create a class like this:
var MyView = Backbone.View.extend({
template: '#my-template',
tagName: function() {
// inspect the template to retrieve the tag name
},
render: function() {
// render the template and append its contents to the current element
}
});
Here's a working example
Backbone.Decarative.Views provides you with an alternative way to do this, without having to rely on setElement. For more, check out my answer here.
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.