Set backbone model attribute value to another model - javascript

Can I set another backbone model as its attribute ?
I have a model that represents a basic entity, I 'd like to reuse it as part of something else.
But, it looks like Backbone's model attribute value can only be String.

Sort answer: yes you can:
myHouse.set({ door: new Door() })
But, in my opinion, is not a good idea to do so, because I don't think Backbone is expecting to found Objects in the Model.attributes. I didn't try but I don't think methods like Model.toJSON are gonna have a correct behavior if someone of the attributes is an Object.
But said so, I don't see any problem to declare real attributes in the Model that make reference to objects like:
myHouse.door = new Door();

If I understood correctly you need to do something like that:
var ModelOne = Backbone.Model.extend({
method : function() {
console.log('ModelOne > method');
},
sayHello : function() {
console.log('ModelOne > hello');
}
});
var ModelTwo = ModelOne.extend({
method : function() {
ModelOne.prototype.method.apply(this);
console.log('ModelTwo > method');
}
});
var methodTwo = new ModelTwo();
methodTwo.method();
methodTwo.sayHello();
Output:
ModelOne > method
ModelTwo > method
ModelOne > hello

One more variation of setting Backbone.Model to the attribute of another model is to set it as default value:
var UserModel = Backbone.Model.extend({
defaults: {
id: null,
name: null,
address: new AddressModel()
}
});
var someUser = new UserModel();
someUser.address.set('country', 'ZA');
When you are doing someUser.save() data enclosed in someUser.attributes.address will be a usual data object.
! One thing not yet tested by me is population of AddressModel upon someUser.fetch()

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

Sails.js : Add custom instace method result as a model property

I'm relatively new to JavaScript programming, so this problem may have a trivial solution. Working with Sails.js, I've created this model.
module.exports = {
tableName: 'FOO_TABLE',
attributes: {
FOO: 'string',
BAR: 'number',
BAR2: function() {
return this.BAR + 1;
}
},
};
Then, in a controller I get all the instances:
FOO_MODEL.find().exec(function(err, FOOS) {
return res.view({data: JSON.stringify(FOOS)});
});
The problem is that inside FOOS, it's not the BAR2 method. I've come with this solution (using Underscore.js):
FOOS = _.map(FOOS, function(FOO){ FOO.BAR2 = FOO.BAR2(); return FOO; });
But I don't see it efficient / smart, as I think I will probably find this problem again. How would you do it? Thank you
If all you want is to set a calculated value for each new instance, you could set BAR2 to be of type number in the model (instead of a function), and add a beforeCreate class method like:
beforeCreate: function(values, cb) {
values.BAR2 = values.BAR + 1;
return cb();
}
If you want to keep BAR2 as an instance method, but have it serialized along with the object, you could override the default toJSON instance method:
toJSON: function() {
var obj = this.toObject();
obj.BAR2 = obj.BAR2();
return obj;
}
Any time an instance is stringified, its toJSON method will be called.
Check Your Query Result
If you're calling your instance-method during a request, namely, within a query-callback -- you may want to check what result you are getting passed in.
In my case, I am using the Sails/Waterline ORM Model-method, find instead of findOne (like a bonehead), which actually argues a collection (array). So I was attempting something along the lines of:
[ {...} ].hasItem(id)
... while what I needed was something like:
myQueryResults[0].hasItem(id)
Or rather, query correctly using findOne. Idiocy is apparently a poor practice, but it happens I guess.
Hope this help!

Backbone: annoying behaviour of prototype object

I understand this is a problem (or behaviour) of javascript itself rather than Backbone's extend method, but I'd like to know what is the best strategy to avoid it.
Let's better put it in code:
var MyModel = Backbone.Model.extend({
value: 0,
values: []
});
var myFirstModel = new MyModel();
myFirstModel.value // 0, as expected
myFirstModel.values // [], as expected
var mySecondModel = new MyModel();
mySecondModel.value = 2;
mySecondModel.values.push(2)
mySecondModel.value // 2, as expected
mySecondModel.values // [2], as expected
myFirstModel.value // 0, as expected
myFirstModel.values // [2], ... WAT!!!
I do understand that the problem is I'm not assigning a new value to mySecondModel.values I'm just operating on the values variable that is in the prototype, that is MyModel.prototype.values (same problem with any other object, of course)
But it's very easy to mess with that. The most intuitive thing is to just think of those as INSTANCE variables, and not variables common to every instance (static or class variables in class based languages).
So far now the general solution I've found is to initialize every variable in the initialize method, like this:
var MyModel = Backbone.Model.extend({
initialize: function() {
this.value = 0;
this.values = [];
}
});
That way everything works as expected, and even though it wouldn't be neccesary for a simple value (like this.value) I find it much easier to just stick to this prnciple in every case.
I'm wondering if there's some better (more elegant, clearer) solution to this problem
This is an effect of JavaScript's prototypical inheritance and the fact that Array objects are reference types. The key/value pairs of the object you pass to extend are copied onto the prototype of MyModel, so they will be shared by all instances of MyModel. Because values is an array, when you modify it, you modify the array for every instance.
What you are doing by setting values inside initialize is called shadowing the prototype, and it is the correct way to solve this issue.
That said, in the case of Backbone.Model, if you are attempting to deal with the model's attributes, you can use the defaults function to provide defaults like this:
var MyModel = Backbone.Model.extend({
defaults: function() {
return {
value: 0,
values: []
}
}
});
Again, this is only for attributes of an instance.
var inst = new MyModel();
// The defaults will be created for each new model,
// so this will always return a new array.
var values = inst.get('values');
For what you are doing, where you are specifying properties on the model itself, it is up to you to set the defaults inside of initialize, as you have done.
Are you intentionally not setting value and values as backbone attributes? If you set attributes on an instance, instead of putting them in the extended backbone model definition, it might work how you expect.
var MyModel = Backbone.Model.extend();
var myFirstModel = new MyModel({
value: 0,
values: []
});
console.log(myFirstModel.get('value'); // 0
console.log(myFirstModel.get('values'); // []
var mySecondModel = new MyModel({
value: 2,
values: [2]
});
//mySecondModel.value = 2;
//mySecondModel.values.push(2)
console.log(mySecondModel.get('value'); // 2
console.log(mySecondModel.get('values'); // [2]
console.log(myFirstModel.get('value'); // 0
console.log(myFirstModel.get('values'); // []
jsFiddle, check the console log.
I too had stumbled across this problem some time back and solved it by defining a defaults method in the model.
var MyModel = Backbone.Model.extend({
defaults: function() {
return {
value: 0,
values: []
}
}
});

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.

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