Nesting Marionette regions, layouts and views - backbone.js

I'm trying to get my Marionette views working in combination with application regions and layouts, but I just can't seem to get the nested views in the layout to render.
Edit: I expected both the OptionsView and BreadcrumbsView to be rendered in the NavigationLayout, which should be rendered in the navigation region. However, the navigation region isn't rendered at all. The console doesn't show any errors.
My structure is as follows:
- Navigation region
- Navigation layout
- Options region
- Breadcrumbs region
- Content region
Assigning an ItemView to the navigation region works as expected.
App = new Backbone.Marionette.Application();
App.addRegions({
'nav': '#nav',
'content': '#content'
});
var NavigationLayout = Backbone.Marionette.Layout.extend({
template: '#nav-template',
regions: {
'breadcrumbs': '#breadcrumbs',
'options': '#options'
}
});
var BreadcrumbsView = Backbone.Marionette.ItemView.extend({
template: '#breadcrumbs-template'
});
var OptionsView = Backbone.Marionette.ItemView.extend({
template: '#options-template'
});
var ContentView = Backbone.Marionette.ItemView.extend({
template: '#content-template'
});
App.addInitializer(function(options) {
var navigationLayout = new NavigationLayout();
App.nav.show(navigationLayout);
App.content.show(new ContentView());
navigationLayout.breadcrumbs.show(new BreadcrumbsView());
navigationLayout.options.show(new OptionsView());
});
$(function() {
App.start();
});
A reduced test case can be found here

Ok, finally found the problem: You can't name a region options in a layout.
All of the regions that are defined in a Layout are directly attached to the layout instance. So, a region defined like this:
Layout.extend({
regions: {
options: "#options"
}
});
ends up setting the layoutInstance.options to the Region instance. This is a problem because Backbone.View defines and uses the options for other purposes.
Renaming the region to anything other than an existing keyword or attribute used by any existing view will fix this.
Layout.extend({
regions: {
optionRegion: "#options"
}
});
Working JSFiddle here: http://jsfiddle.net/tegon/64ovLf64/

Related

MarionetteJS: Application Regions vs. Layouts [duplicate]

This question already has an answer here:
What to use since Marionette Application Regions are deprecated
(1 answer)
Closed 6 years ago.
I was reading the documentation of the latest version (2.3.0) and it is saying that Application Regions are now deprecated.
Application Regions
Warning: deprecated This feature is deprecated. Instead of using the
Application as the root of your view tree, you should use a Layout
View. To scope your Layout View to the entire document, you could set
its el to 'body'. This might look something like the following: var
RootView = Marionette.LayoutView.extend({ el: 'body' });
In most of the tutorials, including David Sulc's book Backbone Marionette: A Gentle Introduction it uses the following code snippet to add regions to an application.
Instead of the following example below, which uses addRegions, what should I be doing instead?
i.e.
var ContactManager = new Marionette.Application({});
ContactManager.addRegions({
mainRegion: "#main-region"
});
var ContactView = Marionette.ItemView.extend({
template: "#whatever",
ui: {
button: ".button".
},
events: {
"click #ui.button": "click",
},
click: function () {
console.log("do stuff here...");
}
});
ContactManager.on("start", function () {
var contactView = new ContactView({
model: someModel
});
ContactManager.mainRegion.show(contactView);
});
Use a layoutview instead.
You could do for example:
var ContactManager = new Marionette.Application({});
var LayoutView = Backbone.Marionette.LayoutView.extend({
template: "#layout-view-template",
regions: {
menu: "#menu",
content: "#content"
}
});
ContactManager.layout_view = new LayoutView();
ContactManager.layout_view.render();
I never actually add regions to my app object directly.

Backbone Layout Manager - Render not called

Here is an example code from the github of backbone layout manager.
The custom render for the view is not getting called/it doesn't stop on the break point.what is happening.
// Create a Content view to be used with the Layout below.
var ContentView = Backbone.Layout.extend({
template: "#content"
});
// Create a new Layout with a sub view for content.
var layout = new Backbone.Layout({
template: "#layout",
// This will place the contents of the Content View into the main
// Layout's <p></p>.
views: {
// Appending a new content view using the array syntax
p: new ContentView({
// Custom render function that reverses everything.
render: function(template, context) {
return template(context).split("").reverse().join("");
}
})
}
});
// Attach the Layout to the main container.
layout.$el.appendTo(".main");
// Render the Layout.
layout.render();
This was answered by the Github when I posted the issue. it should be layout.renderTemplate()

Using regions in a CompositeView for subview

I'm using a CompositeView to create a grid of images that they had some events on it. This is how it looks like:
Backbone.Marionette.CompositeView.extend({
events: {
'click li.feed-thumb': 'clickElement',
},
template: _.template(template),
itemView: ItemFeedView,
itemViewContainer: "#feed ul.feed",
clickElement: function(event) {
var profile = new ProfileFeedView();
}
});
My template for this CompositeView contains a <li> element that will render the profile when I click on a image. I use the same <li> for all the events of click into a image. I would like to handle this as a region, because I understand that doing it as region Marionette will handle the opening and closing of the views.
I think CompositeView do not support a regions: {profileRegion: '#feed-profile'}, what's my options?
Thanks in advance!
You should use a Layout View in which you can specify as many regions as you want, so you can create a list region in which you can put your composite view and a profile region in which you can put a item view that will render the profile.
Marionette's docs -- Layout View
If for some reason you want to have regions in your CompositeView you can also do something like this:
var YourView = Backbone.Marionette.CompositeView.extend({
regions: {
"someRegion": ".someRegionClass"
},
"initialize": function(options) {
this._initializeRegions(options);
},
"onDestroy": function() {
this.regionManager.destroy();
}
})
_.each(["_initializeRegions", "_initRegionManager",
"_buildRegions", "addRegion", "addRegions",
"removeRegion", "getRegion", "getRegions",
"_reInitializeRegions", "getRegionManager"], function(prop) {
PaginatorView.prototype[prop] = Marionette.LayoutView.prototype[prop];
});
To be honest, it works but i haven't tested it for full functionality.
view.someRegion.show(otherView) works.
(also works for other views i guess and you will have to add your other options needed to extend the view of course)
In addition to what Manfred said I implemented it this way on a Marionette Composite View:
View.ListView = Marionette.CompositeView.extend({
template: listTpl,
emptyView: noItemsTpl,
childView: View.ListItem,
childViewContainer: '#items-list',
regions: {
"someRegion": "#someRegion"
},
initialize: function(options) {
//give this composite view a LayoutView behaviour with added region manager
this.regionManager = new Marionette.RegionManager();
_.each(["_initializeRegions", "_initRegionManager",
"_buildRegions", "addRegion", "addRegions",
"removeRegion", "getRegion", "getRegions",
"_reInitializeRegions", "getRegionManager"], function(prop) {
Marionette.CompositeView.prototype[prop] = Marionette.LayoutView.prototype[prop];
});
var that = this;
_.each(this.regions, function(value, key) {
var region = that.addRegion(key, value);
that[key] = region;
});
},
onDestroy: function() {
this.regionManager.destroy();
}
});
This way you will be able to interact with your CompositeView instance the exact same way you do with a LayoutView instance:
var listView = new View.ListView({ ... });
var anotherView = new View.AnotherView({ ... });
listView.someRegion.show(anotherView);

can't get application to work after namespacing

I have a Backbone app that is working properly, however, when I tried to reorganize the code under a namespace I can't get it to do anything. I can't even trigger events (by clicking on ids) for views that I know are getting initialized (through console log messages), so I'm wondering if I've introduced some fundamental flaw somehow. I'm following a pattern set out by this blog (in french) http://www.atinux.fr/2011/12/10/organiser-son-code-backbone-js-en-modules/
In the main application.js (see below), I instantiate all of the views and models after initiating the app on document ready. One change introduced as a result of creating this namespace was setting the models for the views with this.models.game
this.views.clock_view = new this.Views.clockView({ model: this.models.game});
Inside the modules folder, I had a views.js and a models.js. I created each view and object like this, prefaced with app.Views or app.Models accordingly
app.Views.announceView = Backbone.View.extend({
....
app.Views.optionsView = Backbone.View.extend({
...
This app.Views.optionsView is getting initialized (according to a console.log statement in the initializer) but when I click on #new_game, the console.log in the startNewGame is not getting triggered
'click #new_game': 'startNewGame'
// 'click .action_button': 'startNewGame'
},
startNewGame: function() {
console.log("startNewGame");
this.model.new();
},
As a result of the namespacing, one other key change I made was when I created new views inside one of the other views. Under the previous (non-namespaced app), I created individual question items from a QuestionListView
var view = new QuestionListItemView({ model: game });
but now I'm doing
var view = new app.Views.questionListItemView({ model: app.models.game })
because the instance of the model was saved to this.models.game in application.js, however, I also tried using 'this.models.game'
var view = new app.Views.questionListItemView({ model: this.models.game })
Either way, before the games model gets involved, I can't trigger the startNewGame function outlined above, so it's not solely an issue of how to identify the model.
I also wondered whether i should be using this.Views or app.Views after the 'new' when creating new views from within
var view = new app.Views.questionListItemView({ model: this.models.game })
I'd be grateful if you could help me identify any flaws I've introduced.
application.js
var app = {
// Classes
Collections: {},
Models: {},
Views: {},
// Instances
collections: {},
models: {},
views: {},
init: function () {
this.models.game = new this.Models.game();
this.views.story_view = new this.Views.storyView(); #doesn't have a model
this.views.clock_view = new this.Views.clockView({ model: this.models.game});
this.views.field_view = new this.Views.fieldView({ model: this.models.game});
this.views.options_view = new this.Views.optionsView({ model : this.models.game});
this.views.announcement_view = new this.Views.announceView({ model: this.models.game});
this.views.question_list_view = new this.Views.questionListView({ model : this.models.game});
this.views.question_list_item_view = new this.Views.questionListItemView({ model : this.models.game});
}
};
$(document).ready(function () {
app.init();
}) ;
The options view is getting initialized but I can't trigger the startNewGame function when I click that #id
app.Views.optionsView = Backbone.View.extend({
// el: $("#options"),
el: $("#options"),
initialize: function() {
console.log("app views OptionsView initialized");
// this.model.bind("gameStartedEvent", this.removeGetAnswerButton, this);
this.model.bind("gameStartedEvent", this.disableNewGame, this);
},
events: {
'click #demo': 'startDemo',
'click #new_game': 'startNewGame'
// 'click .action_button': 'startNewGame'
},
startDemo: function(){
console.log("start demo");
this.model.demo();
},
startNewGame: function() {
console.log("startNewGame");
this.model.new();
},
disableNewGame: function(){
$('#new_game').attr("disabled", true);
}
});
Update
My file structure looks like this
<%= javascript_include_tag 'application.js'%>
<%= javascript_include_tag 'modules/models'%>
<%= javascript_include_tag 'modules/views'%>
At the top of the views and models file, I just do something like this
app.Views.optionsView = Backbone.View.extend({
ie.. there is no further document ready. In fact, including another document ready in these files breaks the application.js init
Prior to using the namespace, I defined the element this way in the view
el: $("#options")
which, as was pointed out in the comments to this question, is not the ideal way to do it(see #muistooshort comment below), (even though it worked).
Defining the el this way instead
el: '#options'
got it working, and let Backbone "convert it to a node object" on its own.

Backbone.Marionette: CompositeView disappear after collection.reset() is fired

I'm quite new in the world of Backbone and I decided to use Marionette for my first serious project with it.
With some difficulties I managed to set up my app's basic options and routing and I was pretty happy with it, but now I'm facing a blocking problem with a CompositeView that represent a Table.
This View is rendered inside a region of a specific layout, called "grid". This layout has 3 region: the top_controls, table_view and bottom_controls. Since I needed to bind some action on some of the elements of the layout I decided to use it as a View, and to include the "master" collection inside it, so I can just rendered a filtered version of the collection inside the CompositeView, without touching the main one.
From my router I call it in this way:
App.grid = new Grid({collection: Clt});
App.page.show(App.grid);
The structure of the layout is this (I'm using requireJS):
var Grid = Backbone.Marionette.Layout.extend({
className: "container-fluid",
template: gridLayout,
regions: {
top_controls: "#top_controls",
table_view: "#table_view",
bottom_controls: "#bottom_controls",
},
initialize: function(){
this.renderTable(this.collection, true);
},
renderTable: function(collection, fetch){
if(fetch){
collection.fetch({success:function(){
var vista = new CompView({collection: collection});
App.grid.table_view.show(vista);
}});
} else {
var vista = new CompView({collection: collection});
App.grid.table_view.show(vista);
}
},
events: {
"keyup input":"filter_grid"
},
filter_grid: function(e){
var $el = e.currentTarget;
var to_filter = $($el).val();
if(to_filter==""){
this.renderTable(this.collection, false);
} else {
var filtered = this.collection.filter(function(item){
return item.get("link_scheda").toLowerCase() == to_filter;
});
if(filtered.length>0){
var filtro = new AssocCollection();
filtro.reset(filtered);
this.renderTable(filtro, false);
}
}
}
});
return Grid;
The Layout template looks like this:
<div class="row-fluid" id="top_controls"><input type="text" id="filter" class="input"/></div>
<div class="row-fluid" id="table_view"></div>
<div class="row-fluid" id="bottom_controls"><button class='add btn btn-primary'>Add</button></div>
My CompositeView is structured like that:
var AssocView = Backbone.Marionette.CompositeView.extend({
tagName: 'table',
className: 'table table-bordered table-striped',
id: 'tableAssoc',
template: assocTemplate,
itemView: assocRow,
appendHtml: function(collectionView, itemView, index){
collectionView.$("tbody").append(itemView.el);
},
events: {
"click .sort_link":"sort_for_link",
},
sort_for_link: function(){
this.collection.comparator = function(model){
return model.get("link_value");
}
this.collection.sort();
},
onRender: function(){
console.log("render table!");
}
});
return AssocView;
The first display of the table is done right, and the filtering too. The problem occur when
I click the table header with the class "sort_link": the entire Table is wiped away from the HTML while the collection stay the same (I suppode the entire layout is re-rendered). If for example I render the CompositeView in another place, like the app main region, it all works as intended. So I guess to problem it's located inside my Layout declaration.
Any help will be much appreciated!
In your Grid, you need to override the initialEvents method and don't do anything in it.
Grid = Backbone.Marionette.Layout.extend({
initialEvents: function(){},
// ... everything you already have
});
Layout extends from ItemView, and ItemView provides the initialEvents implementation. This method checks to see if it was given a collection, and if it does, it wires up the collection "reset" event to the "render" method of the view. In your case, you are passing the collection through and don't want this behavior. So, overriding the initialEvents method will correct it.
Update: I thought I had removed that initialEvents a long time ago. If you're keeping up to date w/ Marionette versions, grab v0.9.10 (or whatever the latest is) and this problem is gone now.

Resources