Is there a way to re-render child view on any custom JQuery event in Backbone/Marionette? - backbone.js

I want to re-render my child view that is being rendered in a composite view on $(window).resize() event which I have subscribed in onShow() of my composite view. Can we do do that? if yes, is there a preferred way? I want to something like this:
define([
"app",
"views/list-item",
], function(App, ListItem) {
var List= App.CompositeView.extend({
template: "list",
childViewContainer: ".list-items",
childView: ListItem,
onShow: function() {
$(window).on('resize', function() {
//re-render child View(ListItem)
});
}
});
return List;
});

According to this PR CompositeView has a renderChildren method, depending n your version of marionette. If it is old it might be private _renderChildren.
Side note: having global selectors and events inside a view is bad practice. I'd put that logic in a separate script outside of the view.

You can implement 'modelEvents' in parent view. On change of 'modelEvents' call render method which will re-render the view. For example:
"modelEvents" :{
"change:updateView": "render"
}
And toggle the 'updateView' model to call this event.

Related

Backbone View overriding the render function loses childviews

I'm trying to run some code to resize a div after my header is done rendering. I have looked at answers here and the Backbone documentation. this is what I wrote:
Backbone.View.extend({
template: header_tpl,
render: function() {
this.$el.html(this.template({});
setTimeout(function() {
$(window).on("resize",function(){
$(".somediv").height($(".someotherdiv").height())
})
.resize()
}, 0);
return this;
},
childViews: {
// Some childViews in here
}
});
This works, but the childViews in this view won't render. I think it has to do with the empty object being passed on the this.template(). The backbone docs say to pass on this.model.attributes, but this view doesn't have a model. Its a simple header with no data being passed on to it.
As pointed out by #CoryDanielson 's comment, Backbone has no default handling of "childViews". If your job is to make a Backbone View render it's child Views, there are lots of reasonably simple ways to do that.
But I think what you are really trying to do is to keep some sort of pre-built render functionality that is built into Backbone.View somewhere else in your codebase. Since the only extension you seem to need is attaching a resize event to window, maybe the best option is to not do this in the render method, then you can continue to use whatever is pre-built elsewhere in your codebase.
Backbone.View.extend({
template: header_tpl,
// no override of render
initialize: function() {
setTimeout(function() {
$(window).on("resize",function(){
$(".somediv").height($(".someotherdiv").height())
})
.resize()
}, 0);
},
childViews: {
// Some childViews in here
}
});
This code should attach the event when the view is instanced, not at each render.
Of course, if your Codebase may also be altering the default initialize method, we really can't know. In that case, there might be some options to override the default methods (initialize, render, ...) just by extending, but still calling the old methods under the hood.

Backbone subviews events not binded correctly [duplicate]

I am working on a Backbone application which is based on a dynamic template . I have a header view, a side panel view and footer view that are dynamically initialized when calling any other view .
The problem is I have events on each template view that are not firing. For example i have a button that changes the language in the header view but its event isn't firing.
My header View :
define([ "jquery", "backbone", "text!../../pages/header.html" ], function($,
Backbone, headerTpl) {
var header = Backbone.View.extend({
events : {
"click #enBtn":"swichToEnglish",
"click #frBtn":"swichToFrench"
},
initialize : function(options) {
_.bindAll(this, "swichToFrench","swichToEnglish");
this.render(options.parent);
//$("#enBtn").css("pointer-events","none");
},
render : function(parent) {
this.template = _.template(headerTpl);
$(parent).append(this.template);
return this;
},
swichToFrench:function(){
console.log("switch to frensh");
if(i18n.currentLocal == 'en'){
i18n.currentLocal='fr';
$("#frBtn").css("pointer-events","auto");
this.render(options.parent);
}
},
swichToEnglish:function(){
console.log("switch to English");
if(i18n.currentLocal == 'fr'){
i18n.currentLocal='en';
$("#enBtn").css("pointer-events","auto");
$("#frBtn").css("pointer-events","none");
this.render(options.parent);
}
}
});
return header;
});
The header view is called in the router :
self._header = new Header({parent: $(".main_container")});
Any ideas how to fix this issue. I need to know haw to fire these events Thank You.
The reason your event handlers is not firing is because the event handlers are delegated to the views element, but you're appending the template to some other element. Since the target elements are in this template which is not appended the view's el, the events will never bubble into the handlers delegated to it.
Apart from that, as #mu is too short pointed out, when you do $(parent).append(this.template);,
this.template is the template function. You should actually call it with the data to get the template.
and you shouldn't be using global selectors like $('') and use this.$el.find('') instead as best practice.
also, options is only available inside the initialize method, and is undefined outside.
Instead of passing the parent into the view and then have it append itself to parent, do that outside the view and make the view independent.
Also declare the template property in the view rather than adding it after the creation as a best practice.
And there's no need to bind the context of event handlers to the view manually, by default the context of event handler will be the view.
Your view should look like:
define([ "jquery", "backbone", "text!../../pages/header.html" ], function($,
Backbone, headerTpl) {
var header = Backbone.View.extend({
initialize : function(options) {
this.render();
},
template: _.template(headerTpl),
events : {
"click #enBtn":"swichToEnglish",
"click #frBtn":"swichToFrench"
},
render : function() {
this.$el.append(this.template(/* pass the data here*/));
//--------^----- this is required for the events to work
return this;
},
swichToFrench:function(){
if(i18n.currentLocal == 'en'){
i18n.currentLocal='fr';
this.$el.find("#frBtn").css("pointer-events","auto");
this.render();
}
},
swichToEnglish:function(){
if(i18n.currentLocal == 'fr'){
i18n.currentLocal='en';
this.$el.find("#enBtn").css("pointer-events","auto");
this.$el.find("#frBtn").css("pointer-events","none");
this.render();
}
}
});
return header;
});
Which you can create like:
self._header = new Header();
$(".main_container").append(self._header.el);
it looks like you just want the view content to be added to '.main_container', and doesn't need another element. In that case you can make your views el point to it rather than creating a new element by passing it as el in the options like:
self._header = new Header({
el: '.main_container' // this.el will refer to `.main_container` in view
});
then you don't have to do $(".main_container").append(self._header.el);
if you must pass the parent into view as an options for some reason, then you should cache it in the view inside initialize so that you can refer it elsewhere like.
this.parent = options.parent
side note:
As you can see, I've changed the order in which you had declared the view's properties - initialize on top followed by template, event and render.
We initialize the view, we create the templating function, we declare the events to be delegated, and then we render the view.
The order in which you define properties doesn't matter internally, but when another developer looks at your code, it's much easier to digest. But it's a matter of opinion.

Backbone click event not firing in template View

I am working on a Backbone application which is based on a dynamic template . I have a header view, a side panel view and footer view that are dynamically initialized when calling any other view .
The problem is I have events on each template view that are not firing. For example i have a button that changes the language in the header view but its event isn't firing.
My header View :
define([ "jquery", "backbone", "text!../../pages/header.html" ], function($,
Backbone, headerTpl) {
var header = Backbone.View.extend({
events : {
"click #enBtn":"swichToEnglish",
"click #frBtn":"swichToFrench"
},
initialize : function(options) {
_.bindAll(this, "swichToFrench","swichToEnglish");
this.render(options.parent);
//$("#enBtn").css("pointer-events","none");
},
render : function(parent) {
this.template = _.template(headerTpl);
$(parent).append(this.template);
return this;
},
swichToFrench:function(){
console.log("switch to frensh");
if(i18n.currentLocal == 'en'){
i18n.currentLocal='fr';
$("#frBtn").css("pointer-events","auto");
this.render(options.parent);
}
},
swichToEnglish:function(){
console.log("switch to English");
if(i18n.currentLocal == 'fr'){
i18n.currentLocal='en';
$("#enBtn").css("pointer-events","auto");
$("#frBtn").css("pointer-events","none");
this.render(options.parent);
}
}
});
return header;
});
The header view is called in the router :
self._header = new Header({parent: $(".main_container")});
Any ideas how to fix this issue. I need to know haw to fire these events Thank You.
The reason your event handlers is not firing is because the event handlers are delegated to the views element, but you're appending the template to some other element. Since the target elements are in this template which is not appended the view's el, the events will never bubble into the handlers delegated to it.
Apart from that, as #mu is too short pointed out, when you do $(parent).append(this.template);,
this.template is the template function. You should actually call it with the data to get the template.
and you shouldn't be using global selectors like $('') and use this.$el.find('') instead as best practice.
also, options is only available inside the initialize method, and is undefined outside.
Instead of passing the parent into the view and then have it append itself to parent, do that outside the view and make the view independent.
Also declare the template property in the view rather than adding it after the creation as a best practice.
And there's no need to bind the context of event handlers to the view manually, by default the context of event handler will be the view.
Your view should look like:
define([ "jquery", "backbone", "text!../../pages/header.html" ], function($,
Backbone, headerTpl) {
var header = Backbone.View.extend({
initialize : function(options) {
this.render();
},
template: _.template(headerTpl),
events : {
"click #enBtn":"swichToEnglish",
"click #frBtn":"swichToFrench"
},
render : function() {
this.$el.append(this.template(/* pass the data here*/));
//--------^----- this is required for the events to work
return this;
},
swichToFrench:function(){
if(i18n.currentLocal == 'en'){
i18n.currentLocal='fr';
this.$el.find("#frBtn").css("pointer-events","auto");
this.render();
}
},
swichToEnglish:function(){
if(i18n.currentLocal == 'fr'){
i18n.currentLocal='en';
this.$el.find("#enBtn").css("pointer-events","auto");
this.$el.find("#frBtn").css("pointer-events","none");
this.render();
}
}
});
return header;
});
Which you can create like:
self._header = new Header();
$(".main_container").append(self._header.el);
it looks like you just want the view content to be added to '.main_container', and doesn't need another element. In that case you can make your views el point to it rather than creating a new element by passing it as el in the options like:
self._header = new Header({
el: '.main_container' // this.el will refer to `.main_container` in view
});
then you don't have to do $(".main_container").append(self._header.el);
if you must pass the parent into view as an options for some reason, then you should cache it in the view inside initialize so that you can refer it elsewhere like.
this.parent = options.parent
side note:
As you can see, I've changed the order in which you had declared the view's properties - initialize on top followed by template, event and render.
We initialize the view, we create the templating function, we declare the events to be delegated, and then we render the view.
The order in which you define properties doesn't matter internally, but when another developer looks at your code, it's much easier to digest. But it's a matter of opinion.

How to get active Backbone view correctly?

I have Backbone view that has sub-views and each of the could stay "active" (just click or contextmenu). And I need to get view reference to that active sub-view from parent view. What is the correct way to do it?
My view hierarchy looks like the following:
var OuterView = Backbone.View.extend({
initialize: function() {
this.children = {};
this.child = new Backbone.View();
this.children[this.child.cid] = this.child;
},
render: function() {
this.$el.html('<div data-view-cid="' + this.child.cid + '"></div>');
_.each(this.children, function(view, cid) {
this.$('[data-view-cid="' + cid + '"]').replaceWith(view.el);
}, this);
}
};
The approach I prefer is not to have active and inactive views, but to only render the view that is active, and to remove them when not needed.
In other words, the easiest way to handle state is to make things stateless.
Simplest solution would be to delegate the parent view to listen to 'click div' and in the callback get the child view by $(event.currentTarget).closest('[data-view-cid]')
The child view should not be aware of the parent view to avoid creating zombies, otherwise you might have to clean up the references.

events not firing after re-render in backbone.js

I am facing a problem while trying to click submit after re-render.
This is my view:
ShareHolderInfoView = Backbone.View.extend( {
template : 'shareholderinfo',
initialize: function() {
this.model = new ShareHolderInfoModel();
},
render : function() {
$.get("shareholderinfo.html", function(template) {
var html = $(template);
that.$el.html(html);
});
//context.loadViews.call(this);
return this;
},
events:{
"change input":"inputChanged",
"change select":"selectionChanged",
"click input[type=submit]":"showModel"
},
inputChanged:function(event){
var field = $(event.currentTarget);
var data ={};
data[field.attr('id')] = field.val();
this.model.set(data);
},
showModel:function(){
console.log(this.model.attributes);
alert(JSON.stringify(this.model.toJSON()));
}
});
This is my Router
var shareholderInfo, accountOwnerInfo;
App.Router = Backbone.Router.extend({
routes:{
'share':'share',
'joint':'joint'
},
share:function(){
$("#subSection").empty();
if(!shareholderInfo){
shareholderInfo = new ShareHolderInfoView();
$("#subSection").append(shareholderInfo.render().el);
} else{
$("#subSection").append(shareholderInfo.$el);
}
},
joint:function(random){
$("#subSection").empty();
if(!accountOwnerInfo){
accountOwnerInfo = new AccountOwnerInfoView();
$("#subSection").append(accountOwnerInfo.render().el);
} else{
$("#subSection").append(accountOwnerInfo.$el);
}
}
});
This is my HTML a div with id='subSection'.
if I check in console, I can able to see the events bound to that view.
Object {change input: "inputChanged", change select: "selectionChanged", click input[type=submit]: "showModel"}
But its not calling that showModel function afer i click submit. Please help.
Your fundamental problem is that you're improperly reusing views.
From the fine manual:
.empty()
Description: Remove all child nodes of the set of matched elements from the DOM.
[...]
To avoid memory leaks, jQuery removes other constructs such as data and event handlers from the child elements before removing the elements themselves.
So when you say:
$("#subSection").empty();
you're not just clearing out the contents of #subSection, you're also removing all event handlers attached to anything inside #subSection. In particular, you'll remove any event handlers bound to accountOwnerInfo.el or shareholderInfo.el (depending on which one is already inside #subSection).
Reusing views is usually more trouble than it is worth, your views should be lightweight enough that you can destroy and recreate them as needed. The proper way to destroy a view is to call remove on it. You could rewrite your router to look more like this:
App.Router = Backbone.Router.extend({
routes: {
'share':'share',
'joint':'joint'
},
share: function() {
this._setView(ShareHolderInfoView);
},
joint: function(random){
this._setView(AccountOwnerInfoView);
},
_setView: function(view) {
if(this.currentView)
this.currentView.remove();
this.currentView = new view();
$('#subSection').append(this.currentView.render().el);
}
});
If your views need any extra cleanup then you can override remove on them to clean up the extras and then chain to Backbone.View.prototype.remove.call(this) to call the default remove.
If for some reason you need to keep your views around, you could call delegateEvents on them:
delegateEvents delegateEvents([events])
Uses jQuery's on function to provide declarative callbacks for DOM events within a view. If an events hash is not passed directly, uses this.events as the source.
and you'd say things like:
$("#subSection").append(shareholderInfo.$el);
shareholderInfo.delegateEvents();
instead of just:
$("#subSection").append(shareholderInfo.$el);
I'd strongly recommend that you treat your views and cheap ephemeral objects: destroy them to remove them from the page, create new ones when they need to go on the page.

Resources