Backbone render return this - javascript

I'm trying to figure out some of the 'patterns' to set up a Backbone-project. In the examples below, in the 'render'-function, the author returns an instance of 'this'.
Why is this? Is it specific for the example, or something common for Backbone? I don't see why one should return 'this' in the 'render'-function.
The examples
http://backbonefu.com/2011/08/filtering-a-collection-in-backbone-js/
Calling a jQuery plugin in a Backbone render method

This is just a common practice so you can call render() and to chain another method call.
It is a common pattern that the Views don't insert its HTML content in the page, and this job is done by the instance that instantiate the View in a first place.
Then what you have to write in the code that instantiate the View is something like this:
var myView = new MyView({ model: myModel });
myView.render();
$(myDOMElement).html( myView.el );
But if render() returns the View itself you can write the above code like this:
var myView = new MyView({ model: myModel });
$(myDOMElement).html( myView.render().el );

The meaning of returning this, is for providing chaining possibilities.
For example, lets assume:
var obj = {
prop1 : 0,
method1 : function(){
},
method2 : function(){
}
};
//Then you could do something like:
obj.method1();
obj.method2();
obj.prop1 = 1;
All actions on obj you need to do separately.
Now consider:
var obj = {
prop1 : 0,
method1 : function(){
return this;
},
method2 : function(){
return this;
}
};
//Now you could do these
obj.method1().prop1 = 1;
obj.method1().method2().method1();

Related

Pushing to properties in Backbone [duplicate]

This question already has an answer here:
Backbone View extends is polluted
(1 answer)
Closed 8 years ago.
I spent a lot of time trying to catch a bug in my app. Eventually I set apart this piece of code which behavior seems very strange to me.
var Model = Backbone.Model.extend({
myProperty: []
});
var one = new Model();
var two = new Model();
one.myProperty.push(1);
console.log(two.myProperty); //1!!
What's the reason behind it? Why it acts so? How to avoid this type of bugs in code?
Inheritance in JavaScript is prototypical - objects can refer directly to properties higher up in the prototype chain.
In your example, one and two both share a common prototype, and do not provide their own values for myProperty so they both refer directly to Model.protoype.myProperty.
You should create new myProperty array for each model you instantiate. Model.initialize is the idiomatic place for this kind of initialisation - overriding constructor is unnecessarily complex.
var Model = Backbone.Model.extend({
initialize: function() {
this.myProperty = [];
}
});
Alternatively you could make myProperty as an attribute of the model:
var Model = Backbone.Model.extend({
defaults: function() {
return {
myProperty: []
}
}
});
It is important to note that defaults is a function - if you were to use a simple object you would encounter the same shared reference issue.
Actually its because myProperty is an array, and as you know arrays will be stored by reference. Just to test consider the following code:
var Model = Backbone.Model.extend({
myProperty: [],
messege: ''
});
var one = new Model();
var two = new Model();
one.messege = 'One!';
two.messege = 'Two!';
console.log(one.messege ); // 'One!'
console.log(two.messege ); // 'Two!'
An alternative around this could be:
var Model = Backbone.Model.extend({
constructor: function() {
this.myProperty = [];
Backbone.Model.apply(this);
}
});
var one = new Model();
one.myProperty.push(1);
var two = new Model();
console.log(two.myProperty); // []
The documentation says:
constructor / initialize new Model([attributes], [options])
When creating an instance of a model, you can pass in the initial values of the attributes, which will be set on the model. If you define an initialize function, it will be invoked when the model is created.
In rare cases, if you're looking to get fancy, you may want to override constructor, which allows you to replace the actual constructor function for your model.
So, following the documentation, you'd want to do something like this to get your case running:
var Model = Backbone.Model.extend({
initialize: function() {
this.myProperty = [];
}
});
source: http://backbonejs.org/#Model-extend

Backbone: Id not being set to model

I have tried the following to set an id to my model:
var globalCounter = 1;
var Model = Backbone.Model.extend({
initialize: function () {
this.id = globalCounter;
globalCounter += 1;
}
});
myModel = new Model();
console.log(myMode.get('id')); // prints undefined
How can I set an id to my models?
You need to use the set() function instead (http://jsbin.com/agosub/1/);
var globalCounter = 1;
var Model = Backbone.Model.extend({
initialize: function () {
this.set('id', globalCounter);
globalCounter += 1;
}
});
myModel = new Model();
console.log(myModel.get('id')); // prints 1
You must use :
this.set('id', globalCounter);
instead of this.id = globalCounter;
You are adding the id value to the Model object, but you want to add it to Model.attributes object. And that what is doing Model.set() method.
model.set("key", value) will put the value in model.attributes.key;
model.get("key") will return the value inside model.attributes.key
This is a little weird for new comers to Backbone, but it's a major (and easy) point to get. It's designed so that using model.set(...) will fire change events you can easily catch to update your views.
Backbone and ES6 Update :
The Backbone attribute object is outdates by ES6 getters and setters. Theses functions can overwrite the standard access.
Warning : this is pseudo-code that may be one day used with ES6 !
class MyModel extends Backbone.Model{
get id(){ return this.attributes.id; }
set id(id){ this.attributes.id = id; }
}
This would allow to write :
let myModel = new Model();
myModel.id = 13; // will use myModel.id(13)
console.log (myModel.id); // will show myModel.id()
As of today, this is only a dream of a Backbone 2. After basic searches, I've seen nothing about that coming.

Adding more functions to Backbone Models

I am attempting to add some functions to backbone so that I can communicate with mongodb. Now I know this won't work client side; however, I do like backbone's functionality for server side model logic as well. I noticed that I would be doing a bunch of repeat work if I kept adding the same functionality for each model so decided to create a "app_model" file to extend backbone when I'm server side. I also don't want to override the standard Backbone functions because they will be useful client side.
So let's take this user class for instance:
var Backbone = require('./app_model');
var User = Backbone.Model.extend({
name : "users",
defaults: function() {
return {
username: "default",
role: 2,
created: new Date(),
updated: new Date(),
logged: new Date()
};
},
idAttribute: "username",
/**
* A predefined listing of user roles
*/
userRoles: [
"admin", //0
"author", //1
"user" //2
],
initialize: function() {
if(!!app) {
this.svrInit();
}
}
});
module.exports = User;
And I want to append functions onto backbone by using my "app_model.js" file, which looks something like this currently:
var Backbone = require('backbone'),
Deferred = require('Deferred'),
when = Deferred.when;
Backbone.Model.prototype.svrInit = function() {
//TODO: perhaps the code below should be made static some how so we don't have a bunch of instances of collection
var model = this;
if(!!app.db){
app.db.collection(this.name,function(err,collection){
model.collection = collection;
});
}
};
Backbone.Model.prototype.svrSave = function() {
var model = this.toJSON();
var dfd = new Deferred();
this.collection.insert(model, {safe:true}, function(err, result){
dfd.resolve();
});
return dfd;
};
Backbone.Model.prototype.svrFind = function(options) {
var model = this.toJSON();
var dfd = new Deferred();
this.collection.find(options, {safe:true}, function(err, result){
dfd.resolve();
});
return dfd;
};
module.exports = Backbone;
I ran my tests when I abstracted this out and it seemed to work alright. Is there a better way to do any of this? Any pit falls? I am using the global "app" variable, is that bad? If so what are some ways around it? I do find it ugly that I had to put this.svrInit() inside the init function at the model level is there anyway to automatically make that happen after creation?
So I've been thinking about this question for a couple days and I the cleanest thing I've come up with is something like this:
var MyModel = function( attributes, options ) {
Backbone.Model.apply( this, arguments );
this.specialInitializer();
};
MyModel.extend = Backbone.Model.extend;
_.extend( MyModel.prototype, Backbone.Model.prototype, {
specialInitializer: function() {
// called after the users 'initialize'
console.log("MyModel initialized.", this);
},
otherNewMethod: function() {
// this is just like any other instance method,
// just as if Backbone.Model implemented it
}
} );
So what this does is basically make an entirely new 'kind' of Backbone.Model. One which also calls specialInitializer. If you look at the backbone source just after the constructor definition for Backbone.Model you'll see this is a similar strategy.
Construct the instance.
Call an initializer the implementor is supposed to define.
Extend the prototype with functionality (in their case Backbone.Events, in ours, Backbone.Model).
Your new initializer can of course call whatever else it needs, etc.
As for your other questions about the static collection stuff and global app variable, I'm afraid I don't follow exactly what is going on there since I don't see a definition for app and don't know what you're using the collection for.
Here's a fiddle that demonstrates this with some extra logging and such.
I'm working on a fairly large code-base with 4-5 levels of inheritance in the views. This is the pattern I'm using:
var BaseView = Backbone.Model.extend({
somefunc: function() {
//contents
},
otherfunc: function(a,b,c) {
//contents
},
//...
});
var User = BaseView.extend({
// things in user view can now access somefunc and otherfunc
});
Here's a quick example in a jsfiddle (note the doSearch function being inherited)

Accessing parent class in Backbone

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
};
};

Backbone.js get and set nested object attribute

I have a simple question about Backbone.js' get and set functions.
1) With the code below, how can I 'get' or 'set' obj1.myAttribute1 directly?
Another question:
2) In the Model, aside from the defaults object, where can/should I declare my model's other attributes, such that they can be accessed via Backbone's get and set methods?
var MyModel = Backbone.Model.extend({
defaults: {
obj1 : {
"myAttribute1" : false,
"myAttribute2" : true,
}
}
})
var MyView = Backbone.View.extend({
myFunc: function(){
console.log(this.model.get("obj1"));
//returns the obj1 object
//but how do I get obj1.myAttribute1 directly so that it returns false?
}
});
I know I can do:
this.model.get("obj1").myAttribute1;
but is that good practice?
While this.model.get("obj1").myAttribute1 is fine, it's a bit problematic because then you might be tempted to do the same type of thing for set, i.e.
this.model.get("obj1").myAttribute1 = true;
But if you do this, you won't get the benefits of Backbone models for myAttribute1, like change events or validation.
A better solution would be to never nest POJSOs ("plain old JavaScript objects") in your models, and instead nest custom model classes. So it would look something like this:
var Obj = Backbone.Model.extend({
defaults: {
myAttribute1: false,
myAttribute2: true
}
});
var MyModel = Backbone.Model.extend({
initialize: function () {
this.set("obj1", new Obj());
}
});
Then the accessing code would be
var x = this.model.get("obj1").get("myAttribute1");
but more importantly the setting code would be
this.model.get("obj1").set({ myAttribute1: true });
which will fire appropriate change events and the like. Working example here: http://jsfiddle.net/g3U7j/
I created backbone-deep-model for this - just extend Backbone.DeepModel instead of Backbone.Model and you can then use paths to get/set nested model attributes. It maintains change events too.
model.bind('change:user.name.first', function(){...});
model.set({'user.name.first': 'Eric'});
model.get('user.name.first'); //Eric
Domenic's solution will work however each new MyModel will point to the same instance of Obj.
To avoid this, MyModel should look like:
var MyModel = Backbone.Model.extend({
initialize: function() {
myDefaults = {
obj1: new Obj()
}
this.set(myDefaults);
}
});
See c3rin's answer # https://stackoverflow.com/a/6364480/1072653 for a full explanation.
I use this approach.
If you have a Backbone model like this:
var nestedAttrModel = new Backbone.Model({
a: {b: 1, c: 2}
});
You can set the attribute "a.b" with:
var _a = _.omit(nestedAttrModel.get('a')); // from underscore.js
_a.b = 3;
nestedAttrModel.set('a', _a);
Now your model will have attributes like:
{a: {b: 3, c: 2}}
with the "change" event fired.
There is one solution nobody thought of yet which is lots to use. You indeed can't set nested attributes directly, unless you use a third party library which you probably don't want. However what you can do is make a clone of the original dictionary, set the nested property there and than set that whole dictionary. Piece of cake.
//How model.obj1 looks like
obj1: {
myAttribute1: false,
myAttribute2: true,
anotherNestedDict: {
myAttribute3: false
}
}
//Make a clone of it
var cloneOfObject1 = JSON.parse(JSON.stringify(this.model.get('obj1')));
//Let's day we want to change myAttribute1 to false and myAttribute3 to true
cloneOfObject1.myAttribute2 = false;
cloneOfObject1.anotherNestedDict.myAttribute3 = true;
//And now we set the whole dictionary
this.model.set('obj1', cloneOfObject1);
//Job done, happy birthday
I had the same problem #pagewil and #Benno had with #Domenic's solution. My answer was to instead write a simple sub-class of Backbone.Model that fixes the problem.
// Special model implementation that allows you to easily nest Backbone models as properties.
Backbone.NestedModel = Backbone.Model.extend({
// Define Backbone models that are present in properties
// Expected Format:
// [{key: 'courses', model: Course}]
models: [],
set: function(key, value, options) {
var attrs, attr, val;
if (_.isObject(key) || key == null) {
attrs = key;
options = value;
} else {
attrs = {};
attrs[key] = value;
}
_.each(this.models, function(item){
if (_.isObject(attrs[item.key])) {
attrs[item.key] = new item.model(attrs[item.key]);
}
},this);
return Backbone.Model.prototype.set.call(this, attrs, options);
}
});
var Obj = Backbone.Model.extend({
defaults: {
myAttribute1: false,
myAttribute2: true
}
});
var MyModel = Backbone.NestedModel.extend({
defaults: {
obj1: new Obj()
},
models: [{key: 'obj1', model: Obj}]
});
What NestedModel does for you is allow these to work (which is what happens when myModel gets set via JSON data):
var myModel = new MyModel();
myModel.set({ obj1: { myAttribute1: 'abc', myAttribute2: 'xyz' } });
myModel.set('obj1', { myAttribute1: 123, myAttribute2: 456 });
It would be easy to generate the models list automatically in initialize, but this solution was good enough for me.
Solution proposed by Domenic has some drawbacks. Say you want to listen to 'change' event. In that case 'initialize' method will not be fired and your custom value for attribute will be replaced with json object from server. In my project I faced with this problem. My solution to override 'set' method of Model:
set: function(key, val, options) {
if (typeof key === 'object') {
var attrs = key;
attrs.content = new module.BaseItem(attrs.content || {});
attrs.children = new module.MenuItems(attrs.children || []);
}
return Backbone.Model.prototype.set.call(this, key, val, options);
},
While in some cases using Backbone models instead of nested Object attributes makes sense as Domenic mentioned, in simpler cases you could create a setter function in the model:
var MyModel = Backbone.Model.extend({
defaults: {
obj1 : {
"myAttribute1" : false,
"myAttribute2" : true,
}
},
setObj1Attribute: function(name, value) {
var obj1 = this.get('obj1');
obj1[name] = value;
this.set('obj1', obj1);
}
})
If you interact with backend, which requires object with nesting structure.
But with backbone more easy to work with linear structure.
backbone.linear can help you.

Categories

Resources