Backbone inheritance, merging the render() function - backbone.js

So I have this current situation:
app.Ui.ModalView = Backbone.View.extend({
events: {
},
initialize: function() {
},
render: function() {
var that = this;
var model = this.model.toJSON();
that.$el.html(that.template(_.extend(this.params || {}, {
model: model,
})));
return this;
}
});
and then the inherited view:
app.Views.childView = kf.Ui.ModalView.extend({
template: JST["templates/app/blah/blah-edit.html"],
events: {
},
initialize: function() {
var that = this;
this.events = _.extend({}, app.Ui.ModalView.prototype.events, this.events);
app.Ui.ModalView.prototype.initialize.apply(this, arguments);
},
render: function(){
// add extra logic in this render function, to run as well as the inherited render function?
}
});
So, I don't want to override the parent's render(), but to add extra functionality to it, how would I go about doing that?

Two ways to achieve this: Either you can add explicit support for overriding the behaviour by creating a "render hook" in the base class, or you'll have to call the overridden base method from the superclass method:
Render hook in base class:
app.Ui.ModalView = Backbone.View.extend({
render: function() {
//if this instance (superclass) defines an `onRender` method, call it
if(this.onRender) this.onRender();
//...other view code
}
}
app.Views.childView = kf.Ui.ModalView.extend({
onRender: function() {
//your custom code here
}
});
Call base class method from super class:
app.Views.childView = kf.Ui.ModalView.extend({
render: function() {
//your custom code here
//call the base class `render` method
kf.Ui.ModalView.prototype.render.apply(this, arguments);
}
});

Related

Overriding Knockout View-Model function

I am using knockout and backbone in my application. My test-view.js look like this:
define([
"knockout",
"./base",
"./../viewmodels/test-vm",
"text!./../templates/test-template.html"
],
function(ko, BaseView, TestViewModel, template) {
var TestView = BaseView.extend({
template: template,
initialize: function() {
this.viewModel = new TestViewModel();
},
render: function(){
this.$el.html(template);
return this;
},
postRender: function() {
ko.applyBindings(this.viewModel, this.el);
}
});
return TestView;
});
test-template.html:
<button class="btn" data-bind="click: test">test</button>
and test-vm.js as follows:
define([],
function() {
function TestViewModel() {
var self = this;
self.test = function () {
alert("in test view model");
};
}
return TestViewModel;
});
When I click button, self.test is invoked. My question is how can I extend TestViewModel in another file and override test function to do some specific things? Thanks in advance!
I don't see any reason you can't use the commonly used "Classical inheritance with Object.create" approach as described here: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/create
// Define the base class
var TestViewModel = function () {
this.sharedProperty = true;
};
TestViewModel.prototype.test = function() {
log("testing TestViewModel");
};
// Define the extended class
var ExtendedTestViewModel = function() {
TestViewModel.call(this);
};
// Copy the prototype and reset the constructor
ExtendedTestViewModel.prototype = Object.create(TestViewModel.prototype);
ExtendedTestViewModel.constructor = ExtendedTestViewModel;
// Override the inherited `test` function
ExtendedTestViewModel.prototype.test = function() {
// To call the base method:
// (skip this if you want to completely override this function)
TestViewModel.prototype.test.call(this);
// Add functionality:
log("testing ExtendedTestViewModel");
};
// Create an instance
var etvm = new ExtendedTestViewModel();
// This will log both the inherited message, as well as the extended message
etvm.test();
// ExtendedTestViewModel has all properties that are in TestViewModel
log(etvm.sharedProperty);
// utils
function log(msg) {
var pre = document.createElement("pre");
pre.appendChild(document.createTextNode(msg));
document.body.appendChild(pre);
};

Backbone defaults view events

Is it possible to make a set of default events which exist in every view? For example if every view in my application includes a settings button
events: {
"click #settings" : "goSettings"
},
...
goSettings: function() {
// settings show function
});
How can I can package this event to be included in every view in my application?
The problem is that View#extend simply overwrites existing properties so you can't put your 'click #settings' in a base class and subclass that. However, you can easily replace extend with something of your own that merges events. Something like this:
var B = Backbone.View.extend({
events: {
'click #settings': 'goSettings'
}
}, {
extend: function(properties, classProperties) {
properties.events = _({}).extend(
properties.events || { },
this.prototype.events
);
return Backbone.View.extend.call(this, properties, classProperties);
}
});
And then extend B instead of Backbone.View for your views.
Demo: http://jsfiddle.net/ambiguous/Kgh3V/
You can create a base view with the event(s) and functions, then make your other views inherit from it. I like the pattern described here, because it's simple to set up and easy to override as needed: http://www.scottlogic.com/blog/2012/12/14/view-inheritance-in-backbone.html
A base view looks like this:
var BaseSearchView = function(options) {
this.inheritedEvents = [];
Backbone.View.call(this, options);
}
_.extend(BaseView.prototype, Backbone.View.prototype, {
baseEvents: {},
initialize: function(options) {
// generic initialization here
this.addEvents({
"click #settings" : "goSettings"
});
this.initializeInternal(options);
},
render: function() {
// generic render here
this.renderInternal();
return this;
},
events: function() {
var e = _.extend({}, this.baseEvents);
_.each(this.inheritedEvents, function(events) {
e = _.extend(e, events);
});
return e;
},
addEvents: function(eventObj) {
this.inheritedEvents.push(eventObj);
},
goSettings: function() {
// settings show function
}
});
BaseView.extend = Backbone.View.extend;
And your child classes like this:
var MyView = BaseView.extend({
initializeInternal: function(options) {
// do something
// add event just for this child
this.addEvents({
"click #differentSettings" : "goSettings"
});
},
renderInternal: function() {
// do something
}
});

How to hook async Backbone event to display of HTML

What I am trying to do is make a call to the database and then display the result in some HTML. I have everything working (the data comes back from the database just fine), except I can't figure out to display the data.
I know that fetch() is async, but I'm not sure how to wire it into my collection view. Here is my Backbone:
(function() {
window.App = {
Models: {},
Collections: {},
Views: {},
Router: {}
};
window.template = function(id) {
return _.template( $('#' + id).html() );
};
App.Models.Main = Backbone.Model.extend({
defaults : {
FName: ''
}
});
App.Collections.Mains = Backbone.Collection.extend({
model: App.Models.Main,
initialize: function(mains) {
this.fetch({success: function(main) {
$('#web-leads').html(main);
}});
},
url: '../leads/main_contact'
});
App.Views.Mains = Backbone.View.extend({
tagName: 'ul',
render: function() {
var ul = this.collection.each(this.addOne, this);
return ul;
},
addOne: function(main) {
var mainC = new App.Views.Main({ model: main});
this.$el.append(mainC.render().el);
return this;
}
});
App.Views.Main = Backbone.View.extend({
tagName: 'li',
template: template('mainContactTemplate'),
render: function () {
this.$el.html(this.template(this.model.toJSON()));
return this;
}
});
main = new App.Views.Main();
mains = new App.Collections.Mains(main);
})();
What I need to be able to is call $('#web-leads').html() with the value returned from mains. How do I do that?
The general pattern for this sort of thing in Backbone is:
create a model or collection
pass that model/colleciton to a view
that view registers an event handler on the model/collection
the model/collection triggers an AJAX request (probably in response to a fetch call)
the view's event handler is triggered
the view's event handler updates the page
So, as mu is too short suggested, your best bet is to follow this pattern and have your view bind a handler to your collection's reset event.
It's worth mentioning however that reset won't always be the event you want to bind. For instance, you might not want to respond an AJAX request unless it changed attribute 'X' of the model. In that case you could instead bind to change:X, and then your handler would only be triggered if the AJAX response changed X.
To see all your possible options, see:
http://documentcloud.github.com/backbone/#Events-catalog
You were on the right track just needed to have the view listening to the Collection rather than the collection listening to the view.
The below is your code with the slight modification of who listens to who.
Why? Ideally we want the Collections to know nothing of the Views.
(function() {
window.App = {
Models: {},
Collections: {},
Views: {},
Router: {}
};
window.template = function(id) {
return _.template( $('#' + id).html() );
};
App.Models.Main = Backbone.Model.extend({
defaults : {
FName: ''
}
});
App.Collections.Mains = Backbone.Collection.extend({
model: App.Models.Main,
url: '../leads/main_contact'
});
App.Views.Mains = Backbone.View.extend({
tagName: 'ul',
initialize : function(){
this.collection.on('reset', this.render, this);
},
render: function() {
var ul = this.collection.each(this.addOne, this);
return ul;
},
addOne: function(main) {
var mainC = new App.Views.Main({ model: main});
this.$el.append(mainC.render().el);
return this;
}
});
App.Views.Main = Backbone.View.extend({
tagName: 'li',
template: template('mainContactTemplate'),
render: function () {
this.$el.html(this.template(this.model.toJSON()));
return this;
}
});
mains = new App.Collections.Mains();
main = new App.Views.Main( {'collection' : mains} );
mains.fetch();
})();

Accessing Properties of Parent Backbone View

I have a backbone view that calls to a sub-view:
lr.MapView = Backbone.View.extend({
el: $('#map'),
foo: "bar",
initialize: function() {
var that = this;
_.bindAll(this, "render", "addAllEvents", "addOneEvent");
this.collection = new lr.Events();
this.collection.fetch({
success: function(resp) {
that.render();
that.addAllEvents();
}
});
},
addAllEvents: function() {
this.collection.each(this.addOneEvent);
},
addOneEvent: function(e) {
var ev = new lr.EventView({
model: e
});
},
render: function() {
}
});
Here is the sub-view:
lr.EventView = Backbone.View.extend({
initialize: function() {
_.bindAll(this, "render");
console.log(lr.MapView.foo); // will console.log 'undefined'
},
render: function() {
}
});
I'd like to be able to access properties the parent view within the sub-view, but it isn't working with the above code. For example, how can I access the 'foo' variable within the sub-view?
lr.MapView is a "class", everything that Backbone.View.extend builds will be in lr.MapView.prototype, not in lr.MapView. Run this with the console open and you'll see whats going on:
var MapView = Backbone.View.extend({ foo: 'bar' });
console.log(MapView);
console.log(MapView.prototype);
console.log(MapView.prototype.foo);
Demo: http://jsfiddle.net/ambiguous/DnvR5/
If you're only going to have a single MapView then you can refer to lr.MapView.prototype.foo everywhere:
initialize: function() {
_.bindAll(this, "render");
console.log(lr.MapView.prototype.foo);
}
Note that everywhere includes within lr.MapView instances so your foo will act like a "class variable" from non-prototype based OO languages.
The right way to do this is to use an instance variable for foo and pass the parent view instance to the sub-view instances when they're created:
// In MapView
addOneEvent: function(e) {
var ev = new lr.EventView({
model: e,
parent: this
});
}
// In EventView
initialize: function(options) {
_.bindAll(this, "render");
this.parent = options.parent; // Or use this.options.parent everywhere.
console.log(this.parent.foo);
}
Or better, add an accessor method to MapView:
_foo: 'bar',
foo: function() { return this._foo }
and use that method in EventView:
initialize: function(options) {
// ...
console.log(this.parent.foo());
}
Proper encapsulation and interfaces are a good idea even in JavaScript.
Just a guess, but could you try something like this in MapView:
addOneEvent: function(e) {
var that = this,
ev = new lr.EventView({
model: e,
parentView = that
});
}
And then access it like this:
lr.EventView = Backbone.View.extend({
initialize: function() {
_.bindAll(this, "render");
console.log(this.parentView.foo);
},
render: function() {
}
});

access function in one view from another in backbone.js

I have this structure of views:
window.templateLoaderView = Backbone.View.extend({});
window.PopupView = templateLoaderView.extend({
initialize: function () {
this.PopupModel = new PopupModel();
this.event_aggregator.bind("tasks_popup:show", this.loadTaskPopup);
},
render: function() {
template= _.template($('#'+this.PopupModel.templateName).html());
$(this.el).html(template(this.PopupModel.toJSON()));
$('#'+this.PopupModel.containerID).html(this.el);
},
loadTaskPopup: function() {
this.PopupModel.loadTemplate("popupTask_templateHolder", "/js/templates/popup_task.html", "1", "container_dialog");
this.render();
}
});
window.TaskbarView = templateLoaderView.extend({
initialize: function () {
this.TaskbarModel = new TaskbarModel();
this.PopupModel = new PopupModel();
},
loadTaskbarPopup: function() {
this.event_aggregator.trigger("tasks_popup:show");
}
});
So I would like to runf function in one view form another. As far as I understand, I need to bind them somehow. Is it possible to bind them in initialize function?
I saw here example: Backbone.js - Binding from one view to another? . They creating both objects and than somehow binding them.
Thanks in advance,
I am kind of a fan of using the "Event Aggregator" pattern. I make sure that every view is given a copy of the same event aggregator object and they can all talk to each other through it... kind of like a CB radio :)
Do this before you create any views:
Backbone.View.prototype.event_aggregator = _.extend({}, Backbone.Events);
Now, you can publish/subscribe from anywhere:
window.PopupView = Backbone.View.extend({
initialize: function() {
_.bindAll(this, "loadTaskPopup");
this.model = new PopupModel();
this.event_aggregator.bind("tasks_popup:show", this.loadTaskPopup);
},
loadTaskPopup: function() {
// do something with this.model
}
});
window.TaskbarView = Backbone.View.extend({
loadTaskbarPopup: function() {
this.event_aggregator.trigger("tasks_popup:show")
}
});

Resources