Marionette Layout's onShow function not getting called - backbone.js

I have a controller where I am starting my marionette app and defining a main region. Inside my main region, there is a mainLayout which further contains two sub-regions which i am rendering these two subregions but the onShow/onRender functions for my main itemview don't get called. :
var eventController = my.class{
create: function(){
//This is the model
var mainModel = new model();
this.createEventApp = new Backbone.Marionette.Application();
this.createEventApp.addRegions({
//This is the main container
mainRegion: ".event-container"
});
this.createEventApp.addInitializer(function(options){
var self=this;
//This is the main Item View for the app
new EventView({
model: mainModel,
region: self.mainRegion
});
});
this.createEventApp.start();
}
}
Now my corresponding Item View to this application is like :
var eventView = Backbone.Marionette.ItemView.extend{
//Template for the item view
template: eventViewTemplate;
initialize: function(options){
_.bindAll(this);
this.model = options.model;
this.region = options.region;
//Creating a main layout inside mainRegion of the app
var myLayout = Backbone.Marionette.Layout.extend({
template: EventViewTemplate,
regions: {
//Region 1
//Region 2
}
});
this.mainLayout = new myLayout();
this.region.show(this.mainLayout);
this.region1view = new region1({
model: this.model
});
//same for region 2
//showing both the regions in the layout
this.mainLayout.region1.show(this.region1view);
this.mainLayout.region2.show(this.region2view);
},
onShow: function(){
//I want to do something here but this function doesn't event gets called
}
}

You are never actually rendering your eventView. The onShow method will get called if you actually render the eventView by doing this in your application:
var view = new EventView({
model: mainModel
});
this.mainRegion.show(view);
If you want your eventView to contain another view then you should make it the layout. You can probably combine your layout and your eventView together. If you don't want your eventView to render anything then just make it a controller and put the onShow function on the layout.

Related

My model attribute in my view is being classified as a function

So I am trying to link my view to my model and I am following the instructions perfectly, however when it comes to the model part I am just stumped.
Whenever I try to define the model via instantiation, the model is being classified as a function when I console.log() it out.
But let me show you.
var ListModel = Backbone.Model.extend({
defaults: {
name: "Miles",
last: "Coleman"
}
});
var ListView = Backbone.View.extend({
initialize: function(opts){
this.template = opts.template;
this.render();
},
render: function() {
var data = this.model.toJSON();
console.log(this.model);
// outputs: function (){a.apply(this,arguments)}
}
});
var view = new ListView({
model: ListModel,
el: 'div',
template: _.template('#todo-template')
});
Is there some silly detail that I'm missing here? Thanks!
You're passing the class itself, ListModel, to the view, but a view expects an instance of the class, new ListModel() for example. Try
var view = new ListView({
model: new ListModel(),
el: 'div',
template: _.template('#todo-template')
});
See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Introduction_to_Object-Oriented_JavaScript for more information on classes and instances in JS.

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);

Closing ItemView without Marionette's RegionManager

I have a region App.modalRegion where I show TopicView, a CompositeView that shows a list of lectures links in the sidebar and a content area that plays the lecture video when a link is clicked. Everything works fine, but the VideoItemView is not closed when another link is clicked on.
My question is: When the video content is changed on '.link' click, is there (one of those Marionette's magical ways) the previous VideoItemView could be closed without using a sub-region and layout inside the App.modalRegion region. Here's the code:
App.modalRegion.show( new TopicView({ model: topicModel }) );
TopicView = new Backbone.Marionette.CompositeView.extend( {
template: tpls.TopicTpl,
ui: {
sidebar: "#topic-sidebar",
content: "#topic-content"
},
initialize: function(){
this.listenTo(this.model, "change", this.render);
},
onRender: function(){
this.showContent();
var collection = this.model.get('lectures'),
that = this;
this.ui.sidebar.on('click','.link',function(e) {
e.preventDefault();
var sno = $(this).data("sno");
var vid = new VideoItemView({
model: collection.get(sno),
lec_sno: sno
});
that.ui.content.html(
vid.render().el
);
});
},
showContent : function() {
var list = new LectListCol({
collection: this.model.get('lectures')
});
this.ui.sidebar.html(
list.render().el
);
}
});
I would add a property to your TopicView that would reference your currentVideoItemView.
Just before you render the new VideoItemView, you would check to see if currentVideoItemView is an instance of VideoItemView.
Then you would call the close method on the currentVideoItemView, render your new VideoItemView and then set the currentVideoItemView again.

After Render Event on CompositeView with Backbone.Marionette

I have a Marionette CompositeView with a search panel and the collection of result data.
I would like to call a function when:
the search panel is rendered.
the collection is not rendered yet.
this function should not be called when the collection is rendered.
I did it in this way: (but "afterRender" function get called twice)
// VIEW
App.MyComposite.View = Backbone.Marionette.CompositeView.extend({
// TEMPLATE
template: Handlebars.compile(templates.find('#composite-template').html()),
// ITEM VIEW
itemView: App.Item.View,
// ITEM VIEW CONTAINER
itemViewContainer: '#collection-block',
//INITIALIZE
initialize: function() {
this.bindTo(this,'render',this.afterRender);
},
afterRender: function () {
//THIS IS EXECUTED TWICE...
}
});
How can i do this?
==========================EDIT==================================
I solved it in this way, if you have an observation please let me know.
// VIEW
App.MyComposite.View = Backbone.Marionette.CompositeView.extend({
//INITIALIZE
initialize: function() {
//this.bindTo(this,'render',this.afterRender);
this.firstRender = true;
},
onRender: function () {
if (firstRender) {
//DO STUFF HERE..............
this.firstRender = false;
}
}
});
Marionette provides an onRender method built in to all of it's views, so you can get rid of the this.bindTo(this, 'render', this.afterRender) call:
// VIEW
App.MyComposite.View = Backbone.Marionette.CompositeView.extend({
// TEMPLATE
template: Handlebars.compile(templates.find('#composite-template').html()),
// ITEM VIEW
itemView: App.Item.View,
// ITEM VIEW CONTAINER
itemViewContainer: '#collection-block',
//INITIALIZE
initialize: function() {
// this.bindTo(this,'render',this.afterRender); // <-- not needed
},
onRender: function () {
// do stuff after it renders, here
}
});
But to get it to not do the work when the collection is not rendered, you'll have to add logic to the onRender method that checks whether or not the collection was rendered.
This largely depends on what you're trying to do with the rendering when no items are rendered from the collection.
For example... if you want to render a "No Items Found" message, you can use the built in emptyView configuration for the composite view.
NoItemsFoundView = ItemView.extend({
// ...
});
CompositeView.extend({
emptyView: NoItemsFoundView
});
But if you have some special code that needs to be run and do certain things that aren't covered by this option, then you'll have to put in some logic of your own.
CompositeView.extend({
onRender: function(){
if (this.collection && this.collection.length === 0) {
// do stuff here because the collection was not rendered
}
}
});
Just use onShow function
Backbone.Marionette.ItemView.extend({
onShow: function(){
// react to when a view has been shown
}
});
http://marionettejs.com/docs/marionette.view.html#view-onshow

Backbone view event atacched to all views

I'm doing my first application in backbone and i get a strange thing happening trying to attach an event.
I got this code so far:
//View for #girl, EDIT action
GirlEditView = Backbone.View.extend({
initialize: function(el, attr) {
this.variables = attr;
console.log(attr);
this.render();
},
render: function() {
var template = _.template( $("#girl_edit").html(), this.variables );
$(this.el).html( template );
$("#edit_girl").modal('show');
}
});
//View for #girl
GirlView = Backbone.View.extend({
initialize: function(el, attr) {
this.variables = attr;
this.render();
},
render: function() {
var template = _.template( $("#girl_template").html(), this.variables );
$(this.el).html( $(this.el).html() + template );
},
events: {
"click p.modify": "modify"
},
modify: function() {
//calls to modify view
new GirlEditView({el : $("#edit_girl")}, this.variables);
}
});
//One girl from the list
Girl = Backbone.Model.extend({
initialize: function() {
this.view = new GirlView({el : $("#content")}, this.attributes );
}
});
//all the girls
Girls = Backbone.Collection.extend({
model: Girl,
});
//do magic!
$(document).ready(function() {
//Underscore template modification
_.templateSettings = {
escape : /\{\[([\s\S]+?)\]\}/g,
evaluate : /\{\[([\s\S]+?)\]\}/g,
interpolate : /\{\{([\s\S]+?)\}\}/g
}
//get initial data and fill the index
var list = [];
$.getJSON('girls.json', function(data) {
list = [];
$.each(data, function(key, val) {
list.push( new Girl(val) );
});
var myGirls = new Girls(list);
console.log( myGirls.models);
});
});
As you can see.
I'm using a collection to store all the girls and the data comes from a REST api in ruby.
Each girls create a new model instance and inside i attached a view instance.
I don't know if it's a good practice but i can't think a better way to do it.
Each view makes a content with a unique id. girl-1 girl-2 and go on.
Now, the template have a edit button.
My original idea is to attack the onclick event and trigger the edit view to get rendered.
That is working as expected.
The proble so far is:
When the events triggers, all the collection (girls) fire the edit view, not the one that "owns" the rendered view.
My question is what i'm doing wrong?
Thanks a lot
All the edit-views come up because all the GirlViews are using the same el:
this.view = new GirlView({el : $("#content")}, this.attributes );
and then you render be appending more HTML:
render: function() {
var template = _.template( $("#girl_template").html(), this.variables );
$(this.el).html( $(this.el).html() + template );
}
Backbone events are bound using delegate on the view's el. So, if multiple views share the same el, you'll have multiple delegates attached to the same DOM element and your events will be a mess of infighting.
You have things a little backwards: models do not own views, views watch models and collections and respond to their events. You'll see this right in the documentation:
constructor / initialize new View([options])
[...] There are several special options that, if passed, will be attached directly to the view: model, collection, [...]
Generally, you create a collection, c, and then create the view by handing it that collection:
var v = new View({ collection: c })
or you create a model, m, and then create a view wrapped around that model:
var v = new View({ model: m })
Then the view binds to events on the collection or model so that it can update its display as the underlying data changes. The view also acts as a controller in Backbone and forwards user actions to the model or collection.
Your initialization should look more like this:
$.getJSON('girls.json', function(data) {
$.each(data, function(key, val) {
list.push(new Girl(val));
});
var myGirls = new Girls(list);
var v = new GirlsView({ collection: myGirls });
});
and then GirlsView would spin through the collection and create separate GirlViews for each model:
var _this = this;
this.collection.each(function(girl) {
var v = new GirlView({ model: girl });
_this.$el.append(v.render().el);
});
Then, GirlView would render like this:
// This could go in initialize() if you're not certain that the
// DOM will be ready when the view is created.
template: _.template($('#girl_template').html()),
render: function() {
this.$el.html(this.template(this.model.toJSON());
return this;
}
The result is that each per-model view will have its own distinct el to localize the events. This also makes adding and removing a GirlView quite easy as everything is nicely wrapped up in its own el.

Resources