Accessing parent class in Backbone - javascript

I need to call the initialize method of the parent class, from inside the inherited MyModel-class, instead of completely overwriting it as I am doing today.
How could I do this?
Here's what my code looks right now:
BaseModel = Backbone.Model.extend({
initialize: function(attributes, options) {
// Do parent stuff stuff
}
});
MyModel = BaseModel.extend({
initialize: function() {
// Invoke BaseModel.initialize();
// Continue doing specific stuff for this child-class.
},
});

Try
MyModel = BaseModel.extend({
initialize: function() {
BaseModel.prototype.initialize.apply(this, arguments);
// Continue doing specific stuff for this child-class.
},
});

MyModel = BaseModel.extend({
initialize: function() {
MyModel.__super__.initialize.apply(this, arguments);
// Continue doing specific stuff for this child-class.
},
});

This worked for me, when I was trying to inherit among my models:
MyModel.prototype.initialize.call(this, options);
Referenced from http://documentcloud.github.com/backbone/#Model-extend
Thanks.

I think it'd be
MyModel = BaseModel.extend({
initialize: function() {
this.constructor.__super__.initialize.call(this);
// Continue doing specific stuff for this child-class.
},
});

this seems to be almost a duplicate of Super in Backbone, so you want something like this:
Backbone.Model.prototype.initialize.call(this);

Similar to #wheresrhys, but I would use apply instead of call in case BaseModel.initialize is expecting arguments. I try to avoid processing the attributes map that can be passed to a Backbone Model upon initialization, but if the BaseModel were actually a View or a Collection then I might want to set options.
var MyModel = BaseModel.extend({
initialize: function() {
this.constructor.__super__.initialize.apply(this, arguments);
// Continue doing specific stuff for this child-class.
},
});

here's a multi generation callSuper method, just add it to your extending class.
callSuper: function (methodName) {
var previousSuperPrototype, fn, ret;
if (this.currentSuperPrototype) {
previousSuperPrototype = this.currentSuperPrototype;
// Up we go
this.currentSuperPrototype = this.currentSuperPrototype.constructor.__super__;
} else {
// First level, just to to the parent
this.currentSuperPrototype = this.constructor.__super__;
previousSuperPrototype = null;
}
fn = this.currentSuperPrototype[methodName];
ret = (arguments.length > 1) ? fn.apply(this, Array.prototype.slice.call(arguments, 1)) : fn.call(this);
this.currentSuperPrototype = previousSuperPrototype;
return ret;
}

You might consider rewriting your code using functional inheritance.
var BackBone=function(){
var that={};
that.m1=function(){
};
return that;
};
var MyModel=function(){
var that=BackBone();
var original_m1=that.m1;
//overriding of m1
that.m1=function(){
//call original m1
original_m1();
//custom code for m1
};
};

Related

Wrap static function (IIFE) into prototype

I wrote a function that I thought I'd only need to process 1 object but it turns out I need more than 1. I will use a simple example:
var Helper = (function () {
return {
el: null
init: function(el) {
this.el = el;
}
doStuff: function(){
// Modify this.el in someway
}
};
}());
So then I'd just do Helper.init(el) on page load and then run Helper.doStuff() when I needed it.
Well now I have three elements who need this functionality.
My first idea was to just make it do Helper.init([el1,el2,el3]) and have it work on an array of elements but I may want to treat each element separately.
I am thinking the best approach is to probably turn the Helper IIFE into a "class" with prototype, but I am a bit crunch on time so I was looking for a way to make a wrapper to accomplish what I need.
I was thinking I can just take the function and not immediately execute it, and then somehow store that function into a prototyped function and utilize it that way.
Looking for ideas on how to best do this with minimal code change.
I am thinking the best approach is to probably turn the Helper IIFE into a "class" with prototype, but I am a bit crunch on time...
I wouldn't expect it to take very long.
I was thinking I can just take the function and not immediately execute it, and then somehow store that function into a prototyped function and utilize it that way.
Looking for ideas on how to best do this with minimal code change.
The class pattern is just one pattern in JavaScript, you can use that Helper just like it is as the prototype of other objects, which matches your "minimal changes" requirement. Just use Object.create:
var helper1 = Object.create(Helper);
helper1.init(el);
var helper2 = Object.create(Helper);
helper2.init(el2);
var helper3 = Object.create(Helper);
helper3.init(el3);
// ...
helper1.doStuff();
helper2.doStuff();
helper3.doStuff();
If you add return this; to the end of init, that can be more concise:
var helper1 = Object.create(Helper).init(el);
var helper2 = Object.create(Helper).init(el2);
var helper3 = Object.create(Helper).init(el3);
// ...
helper1.doStuff();
helper2.doStuff();
helper3.doStuff();
Live Example:
var Helper = (function () {
return {
el: null,
init: function(el) {
this.el = el;
return this;
},
doStuff: function(){
this.el.style.color = "green";
this.el.style.fontWeight = "bold";
}
};
}());
var helper1 = Object.create(Helper).init(document.getElementById("el1"));
var helper2 = Object.create(Helper).init(document.getElementById("el2"));
var helper3 = Object.create(Helper).init(document.getElementById("el3"));
// ...
setTimeout(function() {
helper1.doStuff();
}, 400);
setTimeout(function() {
helper2.doStuff();
}, 800);
setTimeout(function() {
helper3.doStuff();
}, 1200);
<div id="el1">el1</div>
<div id="el2">el2</div>
<div id="el3">el3</div>
You could even keep using Helper directly on the first el, further reducing code changes, though I wouldn't recommend it.
Alternately, wrap it in a function that returns it (and here I've also included that change to init):
function getHelper() {
var Helper = (function () {
return {
el: null,
init: function(el) {
this.el = el;
return this; // <============== Added
},
doStuff: function(){
// Modify this.el in someway
}
};
}());
return Helper;
}
Then for your three places you need it:
var helper1 = getHelper().init(el);
var helper2 = getHelper().init(el2);
var helper3 = getHelper().init(el2);
// ...
helper1.doStuff();
helper2.doStuff();
helper3.doStuff();
Side note: You don't need the IIFE there anyway unless you have things in it that aren't shown beyond the object initializer...
Just rewritten code:
function Helper (el) {
this.el = el;
}
Helper.prototype = {
doStuff: function(){
// Modify this.el in someway
}
};
var helper1 = new Helper(el1);
var helper2 = new Helper(el2);
var helper3 = new Helper(el3);
helper1.doStaff();
helper2.doStaff();
helper3.doStaff();
Another way, retrieves arguments from arguments object:
var Helper = (function () {
return {
el: null
init: function() {
this.el = Array.from(arguments)
}
doStuff: function(){
this.el.forEach(el => {
// Modify el in someway
});
}
};
}());

Backbone view inheritance

I know someone will mark this as duplicate but I went through almost all posts related to this subject and that's not what I am looking for. So here is the thing bugling my mind since last week.
I have been tasked to create an atomic design of views. There will be a core base view, then another view will extend it and so on. element->panel->window, element->panel->popup etc. With Backbone.View.extend I can simply do it like
var BaseView = Backbone.View.extend({
initialize : function(options) {
this.$el.attr('cid', this.cid);
this.base = options.base || arguments[0]['base'];
this.parent = options.parent || arguments[0]['parent'];
this.children = [];
if(typeof this.template !== 'undefined') {
if (typeof this.template=='function') {
// do nothing. template is already a underscore template and will be parsed when first call;
}
else if (typeof this.template=='string' && this.template.substr(0,1)=='#') {
if ($(this.template).length >0 ) {
this.template = _.template($(this.template).html());
}
else {
console.warn('Template element ' + this.template + 'could not be located in DOM.');
}
}
else {
this.template = _.template(this.template);
}
}
else {
this.template = _.template('<span></span>');
}
if (typeof this.parent!=='undefined' && this.parent ) {
this.parent.add.apply(this.parent, [this]);
}
},
add: function(child){
this.children.push(child);
},
getChildren : function(){
return this.children;
},
clean: function(){
this.$el.empty();
},
close: function () {
BaseView.prototype.clear.apply(this, [true]);
this.undelegateEvents();
this.unbind();
this.stopListening();
this.remove();
},
clear: function(){
if (this.children.length > 0) {
empty = empty || false;
_.each(this.getChildren(), function (child, index) {
Baseview.prototype.close.apply(child);
});
this.children = [];
if (empty) {
this.$el.empty();
}
}
return this;
}
})
then if I try use it as
var Layout = new BaseView.extend({
el: '#someElement',
template : '#sometemplate',
initialize : function(){
this.childView = new ChildView({parent: this, base: this, collection: someCollection});
return this;
},
render: function(){
this.clean().$el.append(this.template({}));
this.$('.content').append(this.childView.render().$el);
return this;
},
});
var ChildView = BaseView.extend({
tagName : 'div',
template : '#childTemplate',
initialize : function(){
return this;
},
render: function(){
var self = this;
this.clean().$el.append(this.template({}));
this.$list = this.$('ul');
_.each( this.collection.models, function(model){
var grandChildView = new GrandChildView({parent: self, base: self.base, model: model});
self.$list.append(grandChildView.render().$el);
})
return this;
}
});
var GrandChildView = BaseView.extend({
tagName : 'li',
template : '#grandChildTemplate',
initialize : function(){
return this;
},
render: function(){
this.clean().$el(this.template(this.model.toJSON()));
return this;
}
});
$(function(){
new Layout();
})
doesn't work because instead of running initialize on BaseView, Backbone calls for initiated first and this.template and all others are undefined.
Then I tried to replace it with constructor instead of initialize on BaseView. But then I end up this.$el undefined error because Backbone.View.constructor has not been called yet so no this.$el yet which is being created by _ensureElement
so with some researching the only thing I found was using
Backbone.View.prototype.constructor.apply(this,[options]);
But this also causes similar issue that at the end of Backbone.View, calls for this.initialize.apply(this, [options]), which then goes to child objects initialize instead. So I am stuck and couldn't wrap my head around this.
I also know that I can call parent's initialize function from childview but that's not preferable since there are lots of subviews extending each other. That's the reason I pass parent object to attach later object to it's children.
what I am trying to accomplish is creating a wrapper extended object that I can extend later for another object, but at the same time it should run some common tasks on original base view, attach additional prototype methods then call for callers initialize.
so pseudo
var BaseView {
extend Backbone view with the passed arguments,
check for base, parent arguments and set them
check for template option, if exists then get the element, create template function and replace with the string one,
if there is parent view passed, then attach yourself to children of parent
call your own initialize method,
return
}
If I understand you correctly, you want to run the "parent view's" initialize method when instantiating a child? If that is correct see this post: https://stackoverflow.com/a/8596882/1819684
What you're missing is the note in the backbone docs on "super" as referenced in that post.
Based on your comment I think this is what you're looking for. You have to call the "super" method (method of parent class) explicitly in backbone as shown in the post I referenced. You can do whatever you want/need to do in your "initialize" both before and after your call to the parent method. I also found this: Defining child views without an initialize method which may help.
var BaseView = Backbone.View.extend({
el: '#container1',
initialize: function(){
console.log('base view init');
this.render();
},
render: function(){
this.$el.html("Hello World 1");
}
});
var ChildView = BaseView.extend({
el: '#container2',
initialize: function() {
console.log('before call to parent (BaseView) init');
BaseView.prototype.initialize.apply(this, arguments);
console.log('ChildView init');
},
render: function(){
this.$el.html("Hello World 2");
}
});
var GrandChildView = ChildView.extend({
el: '#container3',
initialize: function() {
console.log('before call to parent (ChildView) init');
ChildView.prototype.initialize.apply(this, arguments);
console.log('GrandChildView init');
},
render: function(){
this.$el.html("Hello World 3");
}
});
var appView = new BaseView();
var appView = new ChildView();
var appView = new GrandChildView();
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.8.3/underscore-min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/backbone.js/1.3.3/backbone.js"></script>
<div id="container1">Loading...</div>
<div id="container2">Loading...</div>
<div id="container3">Loading...</div>

Backbone Collection inheritance, two constructors

I am looking for a way to have a BaseCollection for all my collections for shared behavior.
I was able to successfully do it with shared functions, but I want some initialization logic to occur in the BaseCollection so that all my collections trigger an event 'model-change'. Is there a way for my BaseCollection to have a constructor - is the code below the best way to do it? I assume if I put two initialize functions or two constructor override functions then only one will get called. So it seems I can only use one of them each.
var BaseCollection = Backbone.Collection.extend({
constructor: function () {
var self = this;
this.on('change',function(model,property){
self.trigger('model-change',model,property);
});
Backbone.Collection.apply(this, arguments);
},
parse: function (resp) {
if (resp.success) {
return resp.success;
}
else if (resp.error) {
return this.models;
}
else {
return resp;
}
},
});
var UsersCollection = BaseCollection.extend({
constructor: function () { //this will override the constructor in BaseCollection, but I want both...
this.givenName = '#UsersCollection';
Backbone.Collection.apply(this, arguments);
},
initialize: function (models, opts) {
//do something here
}
});
Is there a better way to do it? One problem that I have is that I can't override constructor twice, I can only override it in the parent (BaseCollection) class. So is there a way to chain constructors? Or do I pretty much only have two options?
You can probably just extend and apply from the BaseCollection instead of the Backbone.Collection to get the results you want from your example:
var BaseCollection = Backbone.Collection.extend({
constructor: function () {
// your base collection constructor
// run the Backbone.Collection constructor on this
Backbone.Collection.apply(this, arguments);
}
});
// extends methods on BaseCollection
var UserCollection = BaseCollection.extend({
constructor: function () {
// your user collection constructor
// run the BaseCollection constructor on this
BaseCollection.apply(this, arguments);
}
});

Setting Backbone attributes in a model in a nested collection

Pretty new to Backbone JS and I need to know the 'right' way of looping through and setting attributes on models in a collection that is within a model.
My models look like this:
var mediaItem = Backbone.Model.extend({
});
var mediaItems = Backbone.Collection.extend({
model: mediaItem
});
var story = Backbone.Model.extend({
initialize: function () {
this.MediaItems = new mediaItems(this.get('MediaItems'));
this.MediaItems.parent = this;
}
});
What I want to do is loop through the MediaItems in a given story and set the width and height of each. If I do it like this...
storyInstance.MediaItems.each(function (mediaItem) {
mediaItem.set('Width', 200);
mediaItem.set('Height', 100);
});
...then the MediaItem models within the storyInstance.MediaItems property are correctly updated, but the objects within storyInstance.attributes.MediaItems are not. And it's the attributes tree that appears to be used when I subsequently call toJSON() on the Story model.
I can probably amend the above to loop through attributes instead, but I get the feeling I've set up the models wrong or there's a more standard way of doing this?
Thanks.
Probably initialize something other than what you expected.
The below code
var story = Backbone.Model.extend({
initialize: function () {
this.MediaItems = new mediaItems(this.get('MediaItems'));
this.MediaItems.parent = this;
}
});
should have been
var story = Backbone.Model.extend({
initialize: function () {
this.MediaItems = this.get('MediaItems');
this.MediaItems.parent = this;
}
});
and instantiating items should be done with instantiation of story model like
var storyInstance = new story({
MediaItems: new mediaItems()
})
then
story.MediaItems.each(function (mediaItem) {
mediaItem.set('Width', 200);
mediaItem.set('Height', 100);
});
would result updating both
Edit: Did not realize this was from '13. It showed up in questions tagged backbone.js and I did not notice the date/time till now.
Try to check for the instance of Array in the initialize section.
var story = Backbone.Model.extend({
initialize: function () {
if(this.get('MediaItems') instanceof Array){
this.MediaItems = new mediaItems(this.get('MediaItems'));
}
else {
this.MediaItems = this.get('MediaItems');
}
this.MediaItems.parent = this;
}
});

Backbone: Easiest way to maintain reference to 'this' for a Model inside callbacks

var JavascriptHelper = Backbone.Model.extend("JavascriptHelper",
{}, // never initialized as an instance
{
myFn: function() {
$('.selector').live('click', function() {
this.anotherFn(); // FAIL!
});
},
anotherFn: function() {
alert('This is never called from myFn()');
}
}
);
The usual _.bindAll(this, ...) approach won't work here because I am never initializing this model as an instance. Any ideas? Thanks.
You could do it by hand:
myFn: function() {
$('.selector').live('click', function() {
JavascriptHelper.anotherFn();
});
}
Or, if anotherFn doesn't care what this is when it is called (or if it wants this to be what live uses):
myFn: function() {
$('.selector').live('click', JavascriptHelper.anotherFn);
}
As an aside, live has been deprecated in favor of on. Also, if you're not instantiating your JavascriptHelper, then why is it a Backbone.Model at all? Why not use a simple object literal:
var JavascriptHelper = {
myFn: function() {
//...
},
anotherFn: function() {
//...
}
};
And what are you expecting this construct:
var JavascriptHelper = Backbone.Model.extend(string, {}, {...})
to leave you in JavascriptHelper? Extending a string is strange but passing three arguments to Backbone.Model.extend is pointless, it only cares about two arguments. If you want static properties then you should be passing them as the second argument:
var JavascriptHelper = Backbone.Model.extend({}, { myFn: ... });

Categories

Resources