In general, how to access parent view from a child view in Backbone?
Specifically, in Backgrid.js, is there a way to access parent row from a cell?
Pass this as an option to the child view on initialization step:
var ChildView = Backbone.View.extend({
initialize : function (options) {
this.parent = options.parent;
}
});
// somewhere in the parent view ...
new ChildView({parent:this});
// You can use this code instead
var ChildView = Backbone.View.extend({
initialize : function (options) {
this._configure(options); // Set all the options as local variables
// This is used in the Backbone.View code on the latest version
}
});
Related
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.
The basic situation is this:
I have a Composite View and an Item View. I construct the Composite view passing it a model and a collection. The model data is used to populate the template for the Composite view. The collection data is used to populate the Item View for the Composite View.
What I want to do is this: in a template helper for the Item view I want to access the model data for the Composite View. I've got as far as accessing the view instance of the Item View. I thought that might give me a handle on the Composite View, from where I could get to its model, but it doesn't.
Is there a way I can do this - access the composite view instance from one of its item view instances?
Thanks
--Justin Wyllie
If you want to access data from the parent CompositeView you can do a number of different things.
Either pass that data directly to the ItemView through the itemViewOptions helper function on the CompositeView. Note: This option has changed to childViewOptions in Marionette 2.
Invoke a method directly on all of the children view from the CompositeView and pass whatever you want into that method.
Trigger an event on or listened for by the ItemView.
None of these options are directly accessing the parent view from the child but should do what you want. Below is code for how to use each of these approaches to pass the CompositeView's model to the children view.
// Pass model into ItemView on init
var MyItemView = Backbone.Marionette.ItemView.extend({
initialize : function (options) {
this.parentsModel = options.parentsModel;
}
});
var MyCompView = Backbone.Marionette.CompositeView.extend({
itemViewOptions : function () { return { parentsModel: this.model }; }
itemView : MyItemView
});
// Invoke function on ItemView, passing data in
var MyItemView = Backbone.Marionette.ItemView.extend({
doSomethingWithParent : function (parentModel) {
// do cool thing with parentModel
}
});
var MyCompView = Backbone.Marionette.CompositeView.extend({
itemView : MyItemView,
onRender : function () {
this.children.call("doSomethingWithParent", this.model);
}
});
// Trigger event that ItemView knows about
var MyItemView = Backbone.Marionette.ItemView.extend({
initialize : function () {
this.listenTo(this, "special:event", function (parentModel) {
// Do cool things
});
}
});
var MyCompView = Backbone.Marionette.CompositeView.extend({
itemView : MyItemView,
onRender : function () {
this.children.each(_.bind(function (childView) {
childView.trigger("special:event", this.model);
}, this));
}
});
I didn't answer the question. But changing the approach works. Instead of trying to access the 'parent' Composite View from the Item View I access the Item View from the Composite View:
https://github.com/marionettejs/backbone.marionette/blob/master/docs/marionette.collectionview.md#onbeforeitemadded-callback
I can modify the model of the current item view (based on a value in the model of the Composite View).
Thought I'd share how Andrew Hubbs suggestion helped me. I was trying to display a parent model property inline with my item template. I used Marionette's templateHelpers property to do this in combination with one of Andrew's suggestions.
I tried to keep the example brief:
Example Composite template - myView Template:
<h1>Page {{name}}</h1>
<h6>Children</h6>
<ul></ul>
Example item template - myItemTemplate:
{{name}} is child of: {{getParentName}}
Views:
App.module( 'App.View', function( View ){
View.MyItemView = Marionette.ItemView.extend({
initialize: function( options ) {
this.parentModel = options.parentModel;
},
template: myItemTemplate,
tagName: 'li',
templateHelpers: function() {
var view = this;
return {
// Called by item template, returns parent model 'name' property.
getParentName: function() {
return view.parentModel.get('name');
}
};
}
});
View.MyView = Marionette.CompositeView.extend({
template: myViewTemplate,
itemView: View.MyItemView,
itemViewContainer: 'ul',
itemViewOptions: function() {
return { parentModel: this.model };
}
});
});
An example of how this would be implemented:
// example of how implementation
// parent model has an attribute called 'children', which is a collection of models
var children = parent.get('children');
var view = new App.View.MyView( { model: parent, collection: children } );
App.mainRegion.show( view );
Background:
I am making changes to an application that uses backbone.js with Handlebars as the templating engine. After a change event fires I need to create html that is appended to the current DOM structure which is basically just a spit-out of information that is contained in the model. This change needed to fit in the already established application structure.
Issue:
I have created a new view that uses a Handlebars template and the model to create the html. I then instantiate that view and call the render function and append the output using JQuery. What I am noticing is that when the html is rendered the model that is passed in because attributes on the $el instead of filling in the template (like I think it should).
View I'm altering:
$.hart.TestView = Backbone.View.extend({
tagName: "li",
template: Handlebars.compile($('#templateOne').html()),
initialize: function () {
this.model.on('change', function () {
this.createMoreInfoHtml();
}, this);
},
selectSomething: function () {
this.$el.removeClass('policies');
this.createMoreInfoHtml(); //function created for new view stuff
},
createMoreInfoHtml: function () {
var id = this.$el.attr('data-id', this.model.get("ID"));
$('.info').each(function () {
if ($(this).parent().attr('data-id') == id
$(this).remove();
});
var view = new $.hart.NewView(this.model, Handlebars.compile($("#NewTemplate").html()));
$('h1', this.$el).after(view.render().el);
},
render: function () {
... //render logic
}
});
View I Created:
$.hart.NewView = Backbone.View.extend({
initialize: function (model, template) {
this.model = model;
this.template = template;
},
render: function () {
this.$el.html(this.template({ info: this.model }));
this.$el.addClass('.info');
return this;
}
});
Json the is the model:
{
"PetName":"Asdfasdf",
"DateOfBirth":"3/11/2011 12:00:00 AM",
"IsSpayNeutered":false,
"Sex":"F",
"SpeciesID":2,
"ID":"ac8a42d2-7fa7-e211-8ef8-000c2964b571"
}
The template
<script id="NewTemplate" type="text/html">
<span>Pet Name: </span>
<span>{{this.PetName}}</span>
</script>
So now to the question: What am I doing wrong? Why are the properties of the model being created as attributes on the $el instead of filling in the template? Can someone please direct me as to how to get the results I am looking for?
Let's skip the problem Jack noticed.
The way you're creating your view is just wrong. It may work as you get the expected arguments in the initialize function, but it has unexpected behaviors you don't see. See the View's constructor:
var View = Backbone.View = function(options) {
this.cid = _.uniqueId('view');
this._configure(options || {});
Now let's have a look at this _configure method:
_configure: function(options) {
if (this.options) options = _.extend({}, _.result(this, 'options'), options);
_.extend(this, _.pick(options, viewOptions));
And of course...
var viewOptions = ['model', 'collection', 'el', 'id', 'attributes', 'className', 'tagName', 'events'];
Ok here we are... Basically when passing the model as the options argument, you're passing an object with an attributes key (the attributes of your model). But this attributes key is also used in the View to bind attributes to its element! Therefore the behavior your noticed.
Now, other wrong thing. You're compiling your template each time you create a new function, but not using it as a singleton either. Put your template in the view:
$.hart.NewView = Backbone.View.extend({
template: Handlebars.compile($("#NewTemplate").html(),
And change your view's creation to make the whole thing work:
new $.hart.NewView({model: this.model});
Oh, and get rid of this useless initialize method. You're just doing things Backbone already does.
I have a parentview which dynamically creates a child view and I have triggered an event from child view to parent view and I am passing the model of the child view to parent view with some properties set. now in the parent view i have to take the model and add to collection. here is my code
child view trigger code
$(this.el).trigger('added', this.model);
parentview event
events: {
"added": "addNewAttribute"
},
addNewAttribute: function (model) {
console.log(model);
this.collection.add(model);
console.log(this.collection);
}
console.log returns undefined. can someone let me know how to pass the model back to parent view?
thanks
Just faced the same problem!
The thing is that the parent function takes as first parameter the event
and the trigger function passes variables as an array.
so the code should be like:
$(this.el).trigger('added', [this.model]);
parentview event:
events: {
"added": "addNewAttribute"
},
addNewAttribute: function (event,model) {
console.log(model);
this.collection.add(model);
console.log(this.collection);
}
this worked for me.
I may be off base but I'd guess that the child view did not do binding for the function that contains this code:
$(this.el).trigger('added', this.model);
As a result when you get into the function that code is in, "this" doesn't point to the child view, it points to some DOM object or something. Then this.model doesn't point to what you expect.
For example:
initialize : function () {
_.bindAll(this, "addFunction");
}
addFunction : function () {
$(this.el).trigger('added', this.model);
}
According to Backbone.js documentation:
Whenever a UI action causes an attribute of a model to change, the
model triggers a "change" event; all the Views that display the
model's data are notified of the event, causing them to re-render.
So I suppose that render() method should be bound to "change" event by default. However the following code does not work:
TestModel = Backbone.Model.extend({});
TestView = Backbone.View.extend({
render: function() {
alert('render called');
}
});
var mod = new TestModel;
var view = new TestView({model:mod});
mod.change();
It works only if I add explicit bind call:
initialize: function() {
this.model.bind('change', this.render, this);
}
Does this mean that my understanding of default render() callback is not correct and we should always bind render() callback by hand?
Unless something has changed in the last few months, yes, that is the case. This is a good thing, as it gives flexibility as to when views are rendered/re-rendered (for example, some applications might want to render a view only after a model has been persisted on the server, not necessarily when it changes in the browser). If you want your views to always re-render when a model attribute changes, you can extend the default backbone view with your own base view that binds its render method to the model change event, then extend all your concrete views from that. Ex:
MyView = Backbone.View.extend({
initialize: function() {
Backbone.View.prototype.initialize.apply(this, arguments);
this.model.bind('change', this.render);
}
});
MyConcreteView = MyView.extend({...});
var model = new Backbone.Model({...});
var view = new MyConcreteView({model: model});
model.set({prop: 'value'});
You can redefine the Backbone.View constructor to set the render callback by default after creating a new view using the code beneath:
Backbone.View = (function(View) {
// Define the new constructor
Backbone.View = function(options) {
// Call the original constructor
View.apply(this, arguments);
// Add the render callback
if (this.model != null) {
this.model.bind("change", this.render, this);
} else {
// Add some warning or throw exception about
// the render callback not being triggered
}
};
// Clone static properties
_.extend(Backbone.View, View);
// Clone prototype
Backbone.View.prototype = (function(Prototype) {
Prototype.prototype = View.prototype;
return new Prototype;
})(function() {});
// Update constructor in prototype
Backbone.View.prototype.constructor = Backbone.View;
return Backbone.View;
})(Backbone.View);
Now you can create a new view like so:
view = new Backbone.View({model: new Backbone.Model})