I have 3 different views, each of which share this same el value:
el:"<div id='main-inner'>"
and in the render function:
this.$el.html(html);
I was asked to combine all of these onto one page, so I'm trying to make a master view that has a collection of these views and renders each of the views within it. The problem is they each write to the same element, #main-inner and clear it's html before appending it's own html value. What is the proper way to change the el value before rendering from the master/wrapper view? Is this the best way to go about it?
Thanks!
Hand over the el to the view when you initialize your views:
var view1 = new myView({el:'#div1'});
var view2 = new myView({el:'#div2'});
var view3 = new myView({el:'#div3'});
And in your view:
var myView = Backbone.View.extend({
initialize: function(options){
this.el = options.el;
}
});
Now you have Views, each rendering in it's own el.
Hope this can help.
You can define your el as global and use it.
example:
App.Common.el = "";
use :
App.Common.el
Related
I have a model's view set up as follows:
var PlayerView = Backbone.View.extend({
el: "div.player",
template: _.template($("#playerTemplate").html()),
render: function() {
this.$el.append(this.template(this.model.toJSON()));
return this;
}
});
and the main view is set up as follows:
var AppView = Backbone.View.extend({
el: "#players",
render: function() {
var pView = new PlayerView({model: playerModel});
this.$el.append(pView.render().$el);
}
});
If you notice the render of AppView, I am rendering the PlayerView first, and then appending its $el to AppView's $el. I was expecting an error situation here, as it would display PlayerView 2 times to main View as follows:
First, in the pView.render() where I put content in pView and
Second, in the same line where I append pView's $el to main view.
But it just appends the pView only once. How does this work?
I am not sure I have explained my question clearly; I can add more context if required.
Assuming div.player exists in DOM as you mentioned in comments,
When you do pView.render(), it adds the template inside it.
Then when you append pView's element (div.player) to AppView's element (#players), entire div.player is moved into #players.
Your code is working the way it should work.
If you intent to create multiple players, You shouldn't use el option in player view, Instead you should decorate the element created by backbone and create multiple instances of player view.
giving a parent and a child view, I'd like 2 things:
the child view should render itself on instantiation
the child's render method should know the current parent's dom element
In practice, from the parent view, instead of this:
,add_bannerbox_view:function(model, collection, options){
var bannerbox = new BannerBoxView({ model: model });
this.bannerbox_views[model.cid] = bannerbox;
this.bannerbox_container.append(bannerbox.el);
bannerbox.render();
}
I'd like simply this;
,add_bannerbox_view:function(model, collection, options){
//here, BannerBoxView is supposed to render itself from initialize()
this.bannerbox_views[model.cid] = new BannerBoxView({ model: model, parent:this.el });
}
But I was wondering: is passing a parent's elem to the child a good practice? Or does it have some bad drawback?
Loose coupling is almost always preferable to tight coupling. The two best reasons I can think of are:
Reusable. Can be used by anywhere in your app without worrying about dependencies.
Testable. Can be tested independent of other components.
By requiring the child view to have a reference to the parent view, you are promoting tight coupling i.e. the child view becomes dependent on the parent view. This makes reusability extremely difficult, and if you're writing unit tests, you're going to have to instantiate or mock a parent class just so you can test the child. This is unnecessary and tedious.
If really what you're trying to do is have the child view automatically render, just extend the core Backbone.View and include a helper function that your parent views can call.
var MyView = Backbone.View.extend({
renderChild: function(view, options) {
var childView = new view(options);
this.views[options.model.cid] = childView;
this.$el.append(childView.el);
childView.render();
}
});
Then, you can define your parent views like so:
var ParentView = MyView.extend({
add_bannerbox_view: function() {
this.renderChild(BannerBoxView, {model: model});
}
});
The helper function we made will let you instantiate, append and render your child views with a single line of code.
I partially answer to myself. More than circular references (I'm passing only a dom element), drawbacks could arise for the self-appending functionality I'd like to use in child's render() method. The reason is possible memory leaks when having large number of views. There is a good explanation here:
http://ozkatz.github.io/avoiding-common-backbonejs-pitfalls.html
I should use var container = document.createDocumentFragment() in the parent view and then maybe pass container to the child view.
Also, following discussions above, and still not fully convinced of the various points (mine first :P) I'm using sort of bridge code. For now, I like doing this: I don't pass parent's dom element as a constructor argument. Instead, I pass it directly to the child's render(). The code is cleaned out to the bare bones:
//parent
var CustomBannersView = Backbone.View.extend({
initialize:function(){
this.groups_container = $('.groups-container');
this.group_views = {};
this.init();
this.set_events();
}
,init:function(){
//instantiate views without rendering for later use
this.collection.each(function(model){
this.group_views[model.cid] = new GroupView({ model:model, id:'group-' + model.cid });
},this);
}
,render:function(){
var temp_box = document.createDocumentFragment();
//render views without dom refresh. Passing the box.
_.each(this.group_views, function(groupview){ groupview.render(temp_box); });
//add container
this.groups_container.append(temp_box);
}
//dom events ----
,events:{
'click .create-gcontainer-button': function(){
this.collection.add(new Group());
}
}
,set_events:function(){
this.listenTo(this.collection,'add',function(model, collection, options){
//render a single subview, passing the main container
//no refresh problem here since it's a single view
this.group_views[model.cid] = new GroupView({ model: model, id:'group-' + model.cid }).render(this.groups_container);
});
}
});//end view
//child
var GroupView = Backbone.View.extend({
tagName: 'fieldset'
,className: 'group'
,initialize:function(){
this.template = Handlebars.compile($('#group-container').html());
}
,render:function(box){//box passed by parent
this.$el.html(this.template(this.model.toJSON()));
$(box).append(this.$el);
//now I can set things based on dom parent, if needed
return this;
}
});
Case 1: Passing model as in options
var View1 = Backbone.View.extend({
initiliaze:function(){
}
});
Case 2 : Passing model as a param and setting it using this
var View2 = Backbone.View.extend({
initiliaze:function(model){
this.model = model
}
});
var view1 = new View1({model:someModel})
var view2 = new View2(someModel)
It's the same thing, except that in the first case you have less code in your view declaration because Backbone handle setting the model in the view for you (this.model = model).
In general Backbone can handle some parameters for you, you can take a look at the documentation to have more informations about it.
In my backbone app I have a view that needs to be rendered using different templates, dependant on a variable that is passed to the view. I am using require js and want to know if there is smart way of doing this.
Here is how I would call instantiate the new view:
var templateType = 'Template1'
var view = new DetailView({model:thisModel, 'templateType': templateType});
Here is an example of the view:
define(['backbone',
'text!templates/template1.html',
'text!templates/template2.html' ],
function(Backbone,
Template1,
Template2){
var DetailView = Backbone.View.extend({
tagName : 'li',
className: 'detail-tag',
initialize : function(){
this.detailTemplate = _.template( this.options.templateType );
this.render();
},
This does not give me the required result. How do I use the options value to link to the require variable - Template1? I realise I could probably set up a switch statement or something, but that seems a bit messy. Is there a neater way of doing it?
Thanks
I decided to pass the template to the view, instead of trying to match it up with a template included with require. I used this question as a model for my solution:
How to pass a template to a view in Backbone
I am learning Backbone.
I am wondering whether or not a Backbone View always requires a Backbone Model.
For example, let's say I have a panel that contains two child panels. The way I would structure this is with a parent view for the main panel, then two child views for the child panels...
var OuterPanel = Backbone.View.extend({
initialize: function() {
this.innerPanelA = new InnerPanelA(innerPanelAModel);
this.innerPanelB = new InnerPanelB(innerPanelBModel);
},
});
var outerPanel = new OuterPanel();
The parent view is really just a container. It may have some controls in it, but no data that needs to be persisted. Is this the proper way to do it? Or is this bad practice?
Thnx (in advance) for your help
As said in Backbone.View docs
Backbone views are almost more convention than they are code — they
don't determine anything about your HTML or CSS for you, and can be
used with any JavaScript templating library.
In other words, if you don't have a model, don't use a model. On the other hand, I would inject the children models as options to the outer view instance and not rely on global variables, something like this:
var OuterPanel = Backbone.View.extend({
initialize: function(options) {
this.innerPanelA = new InnerPanelA({model: options.modelA});
this.innerPanelB = new InnerPanelB({model: options.modelB});
}
});
var outerPanel = new OuterPanel({
modelA: innerPanelAModel,
modelB: innerPanelBModel
});