backbone.js nested model's object not unique to instance - javascript

I have a parent Model that has by default and nested Model. The nested Model itself has propertyA, which is an object with defaultValue and userValue. The idea is that every instance of parent Model comes with a nested Model that has a userValue of null, and a static default value.
The problem is, when I update the userValue for one instance, it ends up changing for all instances going forward. Instead of updating the userValue for a particular instance, I'm doing something wrong and updating the nested Model "prototype".
Below code available at http://jsfiddle.net/GVkQp/4/. Thanks for help.
var ParentModel = Backbone.Model.extend({
initialize: function(){
var defaultObjs = {
nestedModel : new NestedModel()
};
$().extend(this.attributes, defaultObjs);
}
});
var NestedModel = Backbone.Model.extend({
defaults: {
"propertyA" : {
"defaultValue" : "ABC",
"userValue": null
}
}
});
var ParentView = Backbone.View.extend({
initialize: function(){
var self = this;
var defaultValue = self.model.get("nestedModel").get("propertyA")["defaultValue"];
var userValue = self.model.get("nestedModel").get("propertyA")["userValue"];
var div = $("<div class='box'>defaultValue = <span id='defaultValue'>" +
defaultValue+ "</span>, userValue = <span id='userValue'>" +
userValue + "</span></div>");
var divButton = $('<input type="button" value="Change my userValue to DEF">')
.click(function(){
temp = self.model.get("nestedModel").get("propertyA");
temp.userValue = "DEF";
//wherewas my intention is just to set the userValue for a particular instance,
//generating another instance of ParentView (by clicking button) reveals that
//the userValue is set even for new instances.
//How can I change it such that I only change the userValue of the particular
//instance?
//set value
self.model.get("nestedModel").set({"propertyA": temp});
//update userValue in View
userValue = self.model.get("nestedModel").get("propertyA")["userValue"];
$(this).parent().find("span#userValue").text(userValue);
});
//append divButtont to div
div.append(divButton)
//append div to body
$('body').append(div)
},
});
$("#add").click(function(){
var newBoxView = new ParentView({
model: new ParentModel()
});
});

Thanks for the jsFiddle. It illustrates your problem perfectly.
You see, when you set your "propertyA" in your defaults, the object you create is the default value being copied along with every other nested class you create. For this reason, when you define your defaults, you have the option to define it as a function in order to create the default value new every time. If you do this, you solve your problem:
var ParentModel = Backbone.Model.extend({
defaults: function() {
return {nestedModel: new NestedModel()};
}
});
var NestedModel = Backbone.Model.extend({
defaults: function() {
return {
"propertyA" : {
"defaultValue" : "ABC",
"userValue": null
}
};
}
});

Related

Add new collection to parent view from child view

I have a parent view (which is for car engines) that contains a child view which displays a list of options. One of the options is to add a new collection to the parent view.
My child view init function looks like this:
initialize: function (engine) {
this.engine = engine; //parent object
this.valves = engine.valves; //may or may not be empty
};
Then I have this method that adds the collection(valves) when a button is pressed:
addPerformanceValves: function() {
var self = this;
if (this.valves.lentgh == 0) {
this.valves = new ValveCollection();
this.valves.url = function() {
return '/api/engines/auto/performance/parts/' + self.id + '/valves';
}
}
this.$('.performanceParts').show();
}
So now that I created the new collection, how do I add it to the parent?
There are multiple ways to achieve that.
Passing the parent object down the hierarchy
Like you're already doing, you could call a function from the parent object to pass the new collection.
var Child = Backbone.View.extend({
initialize: function(options) {
options = options || {};
this.engine = options.engine; //parent object
this.valves = engine.valves; //may or may not be empty
},
addPerformanceValves: function() {
var self = this;
if (this.valves.lentgh == 0) {
this.valves = new ValveCollection();
this.valves.url = function() {
return '/api/engines/auto/performance/parts/' + self.id + '/valves';
}
// call the parent
this.engine.addNewCollection(this.valves);
}
this.$('.performanceParts').show();
}
});
var Parent = Backbone.View.extend({
addNewCollection: function(collection) {
// do what you want with the collection
this.newCollection = collection;
}
});
Triggering events
One way to avoid strong coupling is to trigger events from the child view, to which the parent is listening.
var Child = Backbone.View.extend({
initialize: function(options) {
options = options || {};
this.valves = options.valves; //may or may not be empty
},
addPerformanceValves: function() {
var self = this;
if (this.valves.lentgh == 0) {
this.valves = new ValveCollection();
this.valves.url = function() {
return '/api/engines/auto/performance/parts/' + self.id + '/valves';
}
// call the parent
this.trigger('child:collection', this.valves);
}
this.$('.performanceParts').show();
}
});
var Parent = Backbone.View.extend({
initialize: function() {
this.child = new Child({ valves: this.valves });
// listen to child view events
this.listenTo(this.child, 'child:collection', this.addNewCollection);
},
addNewCollection: function(collection) {
// do what you want with the collection
this.newCollection = collection;
}
});
Then, the child view only has what it needs and nothing more. It help to keep responsibilities at the right place.

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

Getting 'type' of a View's model

you can access View's model from the View methods - like render() (through its model property). But let's say you have many different models and use the them with the same type of View, changing view's model property when you need.
How can you determine from the View what type of the model it's using?
var Model1 = Backbone.Model.extend();
var Model2 = Backbone.Model.extend();
var MyView = Backbone.View.extend({
render:function(){
console.log(" you're using: "+ model); // how to determine here is it using Model1 or Model2
}
})
var mv1 = new MyView({model: new Model1()})
var mv2 = new MyView({model: new Model2()})
Objects have identity, not name. If you want to know the name of the constructor that created an object (which can be considered the object's type; see the ECMAScript Specification), the bullet-proof approach is to make that information part of the constructed object:
var Model1 = Backbone.Model.extend({type: "Model1"});
var Model2 = Backbone.Model.extend({type: "Model2"});
var MyView = Backbone.View.extend({
render: function () {
console.log("You are using " + this.model.type);
}
});
var mv1 = new MyView({model: new Model1()});
var mv2 = new MyView({model: new Model2()});
This way, the model objects inherit the type property through the prototype chain. See also http://backbonejs.org/#View-render.
Alternative, less efficient and less reliable solutions include:
Giving the constructor a name and accessing that:
var Model1 = Backbone.Model.extend();
Model1.name = "Model1";
var MyView = Backbone.View.extend({
render: function () {
console.log("You are using " + this.model.constructor.name);
}
});
var mv1 = new MyView({model: new Model1()});
var mv2 = new MyView({model: new Model2()});
(The additional assignment to the name property was not necessary if the constructor was
var Model1 = function Model1 () {
// …
};
or
function Model1 ()
{
// …
}
but that is – unfortunately – not the Backbone.js way.)
Parsing the string representation of a named Function instance such as a constructor.
But that is implementation-dependent:
var Model1 = function Model1 () {
// …
};
var MyView = Backbone.View.extend({
getModelName: function () {
var aFunction = this.model.constructor;
return (aFunction != null && typeof aFunction.name != "undefined" && aFunction.name)
|| (String(aFunction).match(/\s*function\s+([A-Za-z_]\w*)/) || [, ""])[1];
},
render: function () {
console.log("You are using " + this.getModelName());
}
});
(Implemented as jsx.object.getFunctionName(). But again, AIUI not fully possible with Backbone.js.)
Using instanceof as suggested in DashK's answer. But that does not give you information immediately or reliably.
Because you have to put the most significant name in the prototype chain first, and change the method whenever you change the prototype chain, or you
will get false positives and false negatives:
var MyView = Backbone.View.extend({
render: function () {
var modelName = "an unknown model";
if (this.model instanceof Model1)
{
modelName = "Model1";
}
else if (this.model instanceof Model2)
{
modelName = "Model2";
}
else if (this.model instanceof Supermodel)
{
modelName = "Supermodel";
}
console.log("You are using " + modelName);
}
});
Keeping this simple...
var Model1 = Backbone.Model.extend();
var Model2 = Backbone.Model.extend();
var MyView = Backbone.View.extend({
render:function(){
if (this.model instanceof Model1) {
alert("You're using model1");
}
else if (this.model instanceof Model2) {
alert("You're using model2");
}
else {
alert("I've no idea what you're using");
}
}
})
var mv1 = new MyView({model: new Model1()});
var mv2 = new MyView({model: new Model2()});
mv1.render();
mv2.render();

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 view variables being overwritten

I am creating views via a $.each and the myId attribute of all the views always ends up being overwritten by the myId of the last element.
Creation of views
$('.form').each(function() {
// split class name
var classNameArray = $(this).attr('class').split('-');
// get typeId from last part of classname
var myId = classNameArray[classNameArray.length-1];
new myView({ 'el': $('.form-' + myId), 'myId': myId });
});
Initialization of my view
var myView = Backbone.View.extend({
events: {
},
initialize: function() {
var myId = this.options.myId;
},
test: function() {
console.log(myId); // will return myId of last view created
}
});
How can I get the views to keep their unique myId's?
When you write it like this --
initialize: function() {
var myId = this.options.myId;
},
It creates a variable local to the initialize function (that's a Javascript thing, not a Backbone thing).
Try this instead (create a variable local to the View object):
initialize: function() {
this.myId = this.options.myId;
},
test: function() {
console.log(myId); // will return myId of last view created
}
this [myId] is not the [myId] in :
initialize: function() {
var myId = this.options.myId;
}
it is the [myId] from :
$('.form').each(function() {
// split class name
var classNameArray = $(this).attr('class').split('-');
// get typeId from last part of classname
var myId = classNameArray[classNameArray.length-1]; //from this myId!!!
new myView({ 'el': $('.form-' + myId), 'myId': myId });
});
because:
first:
"var myId = this.options.myId; "
this [myId] is the initialize within the local variables,you can not access this variable outside the method.
second:
console.log(myId); // will return myId of last view created
why are we here be able to access to [myId]?because this [myId] is the [this.myId],[this] is the context of the calling method :
new myView({ 'el': $('.form-' + myId), 'myId': myId });
so,this [myId] is the :
var myId = classNameArray[classNameArray.length-1];
[myId] here.this [myId] is cyclic change, so you always get the last cycle the value of the [myId].
solution:
initialize: function() {
this.myId = this.options.myId;
},
because [this.myId] is [myView]'s internal variables, each time you create the instance of [myView], in the [initialize] method dynamically the [this.myId] is set to the correct value form [this.options.myId].
kind of problem, you can refer to this article:
http://dmitrysoshnikov.com/ecmascript/chapter-4-scope-chain

Categories

Resources