I have a model which has an array of lets call them blocks. All blocks have a block-header attribute. Depending on which block-header attribute i have i need to manage the view/model in a different way. So im wondering what way I could use the composite or collection view in marrionette to render a collection of blocks, using different templates depending on the block-header in each model.
My current work around is like this:
App.view.external_report.TemplateSetup = Marrionette.CompositeView.extend({
__name__: 'ExternalReport$TemplateSetup',
template: 'external_report/templatesetup',
className: 'external-report',
super: App.view.CompositeView.prototype,
id: 'template-setup',
events: {
'click #cancel': 'cancel',
'click #save': 'save'
},
initialize: function(options) {
if (!this.model || !this.model.get('template')) throw 'No model or template found';
var templateObj = JSON.parse(this.model.get('template'));
var blocks = templateObj.SubBlocks;
this.blockViews = [];
_.each(blocks, function(block) {
var model = new Backbone.Model(block);
this.blockViews.push(new App.view.external_report.blocks.BaseBlock({
model: model
}))
}.bind(this));
},
onRender: function() {
_.each(this.blockViews, function(blockView) {
blockView.render().then(function() {
this.$el.append(blockView.$el);
}.bind(this));
}.bind(this));
},
save: function() {
this.model.set('template', JSON.stringify(this.generateTemplate()));
this.model.save().then(function() {
//placeholder
}.bind(this));
},
generateTemplate: function() {
var template = JSON.parse(this.model.get('template'));
template.SubBlocks = [];
_.each(this.blockViews, function(blockView) {
template.SubBlocks.push(blockView.generateBlockJSON());
}.bind(this));
return template;
}
});
I think you should create two ItemViews and overwrite the function getChildView to select the ItemView that you want to render.
Another way is create one ItemView and select the template that you want to render overwriting the getTemplate function to select the template that you'll render.
I have a Marionette application which as more than 1 region.
App.addRegions({
pageRegion: '#page',
contentsRegion :'#contents'
});
As part of App.pageRegion, I add a layout.
App.ConfiguratorLayout = Marionette.Layout.extend({
template: '#configurator-page',
regions: {
CoreConfiguratorRegion: '#Core-Configurator-Region',
SomeOtherRegion:'#someOtherregion'
}
});
This layout is rendered before the application starts.
App.addInitializer(function() {
var configLayout = new App.ConfiguratorLayout();
App.pageRegion.show(configLayout);
});
Later on in the application, I just need to change the contents of the configLayout.
I am trying to achieve something like this.
App.pageRegion.ConfiguratorLayout.CoreConfiguratorRegion.show(someOtherLayout);
Is there a way to do this besides using DOM selector on the $el of App.pageRegion.
App.pageRegion.$el.find('#...')
Instead of in an app initializer, move the configLayout initialization code into a controller that can keep a reference to it and then show() something in one of its regions.
// pseudocode:
ConfigController = Marionette.Controller.extend({
showConfig: function() {
var layout = new App.ConfiguratorLayout();
App.pageRegion.show(configLayout);
var someOtherLayout = new App.CoreConfiguratorLayout();
layout.coreConfiguratorRegion.show(someOtherLayout);
// ... maybe create and show() some views here?
}
});
App.addInitializer(function() {
var controller = new ConfigController();
// more likely this would be bound to a router via appRoutes, instead of calling directly
controller.showConfig();
});
The scenario is this: I have a collectionView that gets used in a couple of places. I pass a few options into the view to change certain display aspects (verbiage mostly), since the behavior is exactly the same everywhere.
I'd really like to extend this customization to the emptyView, but I can't find a way to do so. There seems to be no reference to the collectionView on the emptyView, and neither can I seem to access the emptyView from the collectionView, outside of defining it.
Basically, I'd like to be able to do something like this:
var noItemsView = Backbone.Marionette.ItemView.extend({
tagName: "li",
className: "no-results",
template: Handlebars.compile(noResultsTemplate),
}),
leftToggleListView = Backbone.Marionette.CollectionView.extend({
tagName: "ul",
className: "left-toggle-view-list",
emptyView: noItemsView,
initialize: function() {
this.emptyView.model.set("name": "some custom name");
}
});
And then have the noItemsView be able to render {{ name }} within its template.
Is there any way to accomplish this, short of modifying Marionette?
In the collectionView you can use the buildItemView, this function will be called also at the time to build the emptyView
I did a little demo in jsFiddle http://jsfiddle.net/rayweb_on/TN34P/
var leftToggleListView = Backbone.Marionette.CollectionView.extend({
tagName: "ul",
className: "left-toggle-view-list",
emptyView: noItemsView,
ValuethatMakesSense : "I do!",
buildItemView: function(item, ItemViewType, itemViewOptions){
var options = _.extend({model: item}, itemViewOptions);
var name = this.ValuethatMakesSense;
var view = new ItemViewType({name : name});
return view;
}
});
And in the initialize function of your item view you can read the options passed.
var noItemsView = Backbone.Marionette.ItemView.extend({
initialize : function (options) {
var name = this.options.name;
console.log(name);
},
tagName: "li",
className: "no-results",
template: "#noresults"
});
Im using a property inside the collectionView and then reading it/passing it to the empty view in the buildItemView just to test the functionality of the buildItemView function, you can do the proper logic checks and validations there.
It was a while since this question was asked, but it might be helpful to anybody looking:
Marionette CollectionView have an emptyViewOptions property now that works exactly like itemViewOptions but for emptyView:
http://marionettejs.com/docs/v2.4.2/marionette.collectionview.html#collectionviews-emptyviewoptions
Actually you can access to the emptyView in this way:
this.children._views[_.keys(this.children._views)[0]];
Seems like in the new version we will have a method that allows to get an emptyView.
https://github.com/marionettejs/backbone.marionette/pull/727
I'm a noob in backbone.js and JavaScript for that matter... and I'm trying to build a simple widget system with Jquery and backbone.js, but I can't seem to figure out how to get multiple instances of my view to render. I am, however able to get one instance to render... my ultimate goal is to build a system where i can click on a button and have it render a new widget on the screen each time.
here is my code:
<script type="text/template" id="widget-template">
<div class="widget-wrap">
<div class="widget-header">
<span class="widget-title"><%= widgetInfo.get('title') %></span>
<span class="widget-close"></span>
<span class="widget-hide"></span>
<span class="widget-expand"></span>
</div>
<div class="widget-container">
<!-- this is where the widget content goes -->
</div>
</div>
</script>
<script typ="text/javascript">
var baseWidget = Backbone.Model.extend({
defaults: {
title: "Base",
author: "AB",
multipleInstances: false,
description: "This is the base widget",
pathToIcon: "",
state: "Open",
position: {left:0, top:0}
}
});
var widgetCollection = Backbone.Collection.extend({
model: baseWidget
});
var widgetcol = new widgetCollection();
var baseView = Backbone.View.extend({
el: '.wraper',
render: function(pos = {left:0, top:0}) {
var widget = new baseWidget();
widgetcol.add(widget);
console.log(widgetcol.length);
widget.set({'position':pos})
var template = _.template($('#widget-template').html(), {widgetInfo: widget});
this.$el.html(template);
this.$el.offset({top:widget.get('position').top, left:widget.get('position').left})
$('.widget-wrap').draggable({
handle: ".widget-header",
stop: function (event, ui) {
widget.set({position: ui.position});
console.log(widget.get('position'));
}
});
}
});
BV = new baseView();
BV.render({left:0, top:0});
b = new baseView();
b.render({left:500, top:0});
any help would be greatly appreciated, also if I'm doing anything really strangely I would love advice on how to do it better.
When you are setting the el property in a view, youre binding the view to an existing element in the dom, limiting yourself to create only one widget. What you actually want to do is let the view generate the element markup and just append all the generated widgets to a certain parent.
You can do that by setting the tagName, className and id attributes in the view.
For example:
var baseView = Backbone.View.extend({
tagName: 'div',
className: '.wrapper'
...
});
That will generate a div with a class of wrapper that you can append to a container.
Later on, you define a click event to create a new widget each time:
$('button').click(function() {
var newView = new baseView();
newView.render();
$('.container').append(newView.el); // now 'el' correspond to the div.wrapper you just created
});
It is considered a good practice among backbone developers to return this from the view's render method. That way you could mix the last two lines like this:
$('.container').append(newView.render().el);
Also, instead if instanciating the collection before the view's definition, people tend to pass the collection as a property of the constructor parameter:
var collection = new widgetCollection();
BV = new baseView({ collection: collection });
Now you can reference the collection inside the view by simply this.collection.
I am in process of learning and using Backbone.js.
I have an Item model and a corresponding Item view.
Each model instance has item_class and item_id attributes, that I want to be reflected in as the 'id' and 'class' attributes of the corresponding view.
What's the correct way to achieve this ?
Example:
var ItemModel = Backbone.Model.extend({
});
var item1 = new ItemModel({item_class: "nice", item_id: "id1"});
var item2 = new ItemModel({item_class: "sad", item_id: "id2"});
var ItemView = Backbone.View.extend({
});
How should I implement the view so that the the views 'el's will translate to:
<div id="id1" class="nice"></div>
<div id="id2" class="sad"> </div>
In most examples I have seen, the view's el serves as a meaningless wrapper element inside which one has to manually write the 'semantic' code.
var ItemView = Backbone.View.extend({
tagName: "div", // I know it's the default...
render: function() {
$(this.el).html("<div id="id1" class="nice"> Some stuff </div>");
}
});
So when rendered, one gets
<div> <!-- el wrapper -->
<div id="id1" class="nice"> Some stuff </div>
</div>
But this seems like a waste - why have the external div ? I want the el to translate directly into the internal div!
Summary: dynamically set view attributes with model data
http://jsfiddle.net/5wd0ma8b/
// View class with `attributes` method
var View = Backbone.View.extend( {
attributes : function () {
// Return model data
return {
class : this.model.get( 'item_class' ),
id : this.model.get( 'item_id' )
};
}
// attributes
} );
// Pass model to view constructor
var item = new View( {
model : new Backbone.Model( {
item_class : "nice",
item_id : "id1"
} )
} );
This example assumes that you're allowing Backbone to generate a DOM element for you.
The attributes method is called after the properties passed to the view constructor are set (in this case, model), allowing you to dynamically set the attributes with the model data before Backbone creates el.
In contrast to some of the other answers: doesn't hard-code attribute values in the view class, dynamically sets them from model data; doesn't wait until render() to set attr vals; doesn't repeatedly set attr vals in every call to render(); doesn't unnecessarily manually set attr vals on DOM element.
Note that if setting the class when calling Backbone.View.extend or a view constructor (e.g. new Backbone.View), you have to use the DOM property name, className, but if setting it via the attributes hash / method (as in this example) you have to use the attribute name, class.
As of Backbone 0.9.9:
When declaring a View...el, tagName, id and className may now be defined as functions, if you want their values to be determined at runtime.
I mention this in case there's a situation where that would be useful as an alternative to using an attributes method as illustrated.
Using an existing element
If you're using an existing element (e.g. passing el to the view constructor)...
var item = new View( { el : some_el } );
...then attributes won't be applied to the element. If the desired attributes aren't already set on the element, or you don't want to duplicate that data in your view class and another location, then you may want to add an initialize method to your view constructor that applies attributes to el. Something like this (using jQuery.attr):
View.prototype.initialize = function ( options ) {
this.$el.attr( _.result( this, 'attributes' ) );
};
Usage of el, rendering, avoiding the wrapper
In most examples I have seen, the view's el serves as a meaningless wrapper element inside which one has to manually write the 'semantic' code.
There's no reason view.el needs to be "a meaningless wrapper element". In fact, that would often break the DOM structure. If a view class represents a <li> element for example, it needs to be rendered as an <li> -- rendering it as a <div> or any other element would break the content model. You'll likely want to focus on correctly setting up your view's element (using properties like tagName, className, and id) and then rendering its content thereafter.
The options for how to have your Backbone view objects interact with the DOM are wide open. There are 2 basic initial scenarios:
You can attach an existing DOM element to a Backbone view.
You can allow Backbone to create a new element that is disconnected from the document, then somehow insert it into the document.
There are various ways you can generate the content for the element (set a literal string, as in your example; use a templating library like Mustache, Handlebars, etc.). How you should use the el property of the view depends what you're doing.
Existing element
Your rendering example suggests that you have an existing element that you're assigning to the view, although you don't show instantiation of the views. If that's the case, and the element is already in the document, then you may want to do something like this (update the content of el, but don't alter el itself):
render : function () {
this.$el.html( "Some stuff" );
}
http://jsfiddle.net/vQMa2/1/
Generated element
Let's say you don't have an existing element and you allow Backbone to generate one for you. You may want to do something like this (but it's likely better to architect things so that your view isn't responsible for knowing about anything outside itself):
render : function () {
this.$el.html( "Some stuff" );
$( "#some-container" ).append( this.el );
}
http://jsfiddle.net/vQMa2/
Templates
In my case, I'm using templates, e.g.:
<div class="player" id="{{id}}">
<input name="name" value="{{name}}" />
<input name="score" value="{{score}}" />
</div>
<!-- .player -->
The template represents the complete view. In other words, there will be no wrapper around the template -- div.player will be the root or outermost element of my view.
My player class will look something like this (with very simplified example of render()):
Backbone.View.extend( {
tagName : 'div',
className : 'player',
attributes : function () {
return {
id : "player-" + this.model.cid
};
},
// attributes
render : function {
var rendered_template = $( ... );
// Note that since the top level element in my template (and therefore
// in `rendered_template`) represents the same element as `this.el`, I'm
// extracting the content of `rendered_template`'s top level element and
// replacing the content of `this.el` with that.
this.$el.empty().append( rendered_template.children() );
}
} );
In your view just do something like this
var ItemView = Backbone.View.extend({
tagName: "div", // I know it's the default...
render: function() {
$(this.el).attr('id', 'id1').addClass('nice').html('Some Stuff');
}
});
You can set the properties className and id on the root element:
http://documentcloud.github.com/backbone/#View-extend
var ItemView = Backbone.View.extend({
tagName: "div", // I know it's the default...
className : 'nice',
id : 'id1',
render: function() {
$(this.el).html("Some stuff");
}
});
EDIT Included example of setting id based on constructor parameters
If the views are constructed as mentioned:
var item1 = new ItemModel({item_class: "nice", item_id: "id1"});
var item2 = new ItemModel({item_class: "sad", item_id: "id2"});
Then the values could be set this way:
// ...
className: function(){
return this.options.item_class;
},
id: function(){
return this.options.item_id;
}
// ...
I know it's an old question, but added for reference. This seems to be easier in new backbone versions. In Backbone 1.1 the id and className properties are evaluated in the function ensureElement (see from source) using underscore _.result meaning if className or id is a function, it will be called, otherwise its value will be used.
So you could give className directly in the constructor, give another parameter that would be used in the className, etc... Plenty of options
so this should work
var item1 = new ItemModel({item_class: "nice", item_id: "id1"});
var item2 = new ItemModel({item_class: "sad", item_id: "id2"});
var ItemView = Backbone.View.extend({
id: function() { return this.model.get('item_id'); },
className: function() { return this.model.get('item_class'); }
});
The other examples are not showing how to actually grab the data from the model. To dynamically add id and class from the model's data:
var ItemView = Backbone.View.extend({
tagName: "div",
render: function() {
this.id = this.model.get('item_id');
this.class = this.model.get('item_class');
$(this.el).attr('id',this.id).addClass(this.class).html('Some Stuff');
}
});
You need to remove tagName and declare an el.
'tagName' signifies that you want backbone to create an element. If the element already exists in the DOM, you can specify an el like:
el: $('#emotions'),
and later:
render: function() {
$(this.el).append(this.model.toJSON());
}
Try to assign the values in initialize method this will directly assign id and class to the div attribute dynamically.
var ItemView = Backbone.View.extend( {
tagName : "div",
id : '',
class : '',
initialize : function( options ) {
if ( ! _.isUndefined( options ) ) {
this.id = options.item_id;
this.class= options.item_class;
}
},
render : function() {
$( this.el ).html( this.template( "stuff goes here" ) );
}
} );
Here's a minimal way to change the class of the view's element dynamically via a model and update it on model changes.
var VMenuTabItem = Backbone.View.extend({
tagName: 'li',
events: {
'click': 'onClick'
},
initialize: function(options) {
// auto render on change of the class.
// Useful if parent view changes this model (e.g. via a collection)
this.listenTo(this.model, 'change:active', this.render);
},
render: function() {
// toggle a class only if the attribute is set.
this.$el.toggleClass('active', Boolean(this.model.get('active')));
this.$el.toggleClass('empty', Boolean(this.model.get('empty')));
return this;
},
onClicked: function(e) {
if (!this.model.get('empty')) {
// optional: notify our parents of the click
this.model.trigger('tab:click', this.model);
// then update the model, which triggers a render.
this.model.set({ active: true });
}
}
});