Reputation: 4778
I have a backbone view that has the following code within its render function (note the 3 console.log(that.ticketSelector.attributes);
):
if(typeof this.ticketSelector === 'undefined') {
// TODO: first fetch tickets
this.ticketSelector = new DataTable({
collection: that.model.tickets,
icon: "ticket",
title: "Tickets",
customClasses: ["ticket", "subject-selector"],
columns: [
{text: 'Name', modelKey: "name", col: 1},
{text: 'Date', modelKey: "date", col: 2},
{text: 'Owner', modelKey: "owner", col: 3},
{text: 'Description', modelKey: "description", col: 4}
]
});
this.$('.subject-selectors').append(this.ticketSelector.$el);
this.ticketSelector.render().resize();
} else {
this.ticketSelector.render().resize();
}
console.log(that.ticketSelector.attributes);
if(typeof this.alarmSelector === 'undefined') {
// TODO: first fetch tickets
this.alarmSelector = new DataTable({
collection: that.model.alarms,
icon: "warning-sign",
title: "Alarms",
customClasses: ["alarm", "subject-selector"],
columns: [
{text: 'Name', modelKey: "name", col: 1},
{text: 'Date', modelKey: "date", col: 2},
{text: 'Owner', modelKey: "owner", col: 3},
{text: 'Description', modelKey: "description", col: 4}
]
});
this.$('.subject-selectors').append(this.alarmSelector.$el);
this.alarmSelector.render().resize();
} else {
this.alarmSelector.render().resize();
}
console.log(that.ticketSelector.attributes);
if(typeof this.taskSelector === 'undefined') {
// TODO: first fetch tickets
this.taskSelector = new DataTable({
collection: that.model.tasks,
icon: "tasks",
title: "Tasks",
customClasses: ["task", "subject-selector"],
columns: [
{text: 'Name', modelKey: "name", col: 1},
{text: 'Date', modelKey: "date", col: 2},
{text: 'Owner', modelKey: "owner", col: 3},
{text: 'Description', modelKey: "description", col: 4}
]
});
this.$('.subject-selectors').append(this.taskSelector.$el);
this.taskSelector.render().resize();
} else {
this.taskSelector.render().resize();
}
console.log(that.ticketSelector.attributes);
In the console I see:
Object {icon: "ticket", title: "Tickets", columns: Array[4], classes: Array[2]}
Object {icon: "warning-sign", title: "Alarms", columns: Array[4], classes: Array[2]}
Object {icon: "tasks", title: "Tasks", columns: Array[4], classes: Array[2]}
Where I would expect:
Object {icon: "ticket", title: "Tickets", columns: Array[4], classes: Array[2]}
Object {icon: "ticket", title: "Tickets", columns: Array[4], classes: Array[2]}
Object {icon: "ticket", title: "Tickets", columns: Array[4], classes: Array[2]}
Here is my DataTable view:
var DataTable = Backbone.View.extend({
tag: 'div',
className: 'data-table',
initialize: function(opts) {
if(typeof opts.parent !== 'undefined') {
this.parent = opts.parent;
}
if(typeof opts.icon !== 'undefined') {
this.attributes.icon = opts.icon;
}
if(typeof opts.title !== 'undefined') {
this.attributes.title = opts.title;
}
if(typeof opts.columns !== 'undefined') {
this.attributes.columns = opts.columns;
}
if(typeof opts.customClasses !== 'undefined') {
this.attributes.classes = opts.customClasses;
}
},
attributes: {},
template: function() {
var temp;
$.ajax(root + 'templates/data-table.html', {
success: function(data) {
// console.log(data);
temp = Mustache.compile($(data).filter('#data-table-template').html());
},
async: false
});
return temp;
}(),
events: {
},
serialize: function() {
var that = this;
return {
root: root,
icon: that.attributes.icon,
title: that.attributes.title,
columns: that.attributes.columns
};
},
resize: function() {
var that = this;
},
subView: [],
render: function() {
var that = this;
var html = this.template(this.serialize());
this.$el.html(html);
if(that.attributes.classes) {
_.each(that.attributes.classes, function(c) {
that.$el.addClass(c);
});
}
this.collection.each(function(row) {
tempView = new DataTableRow({ model: row, parent: that, columns: that.attributes.columns });
that.subView.push(tempView);
that.$('.tbody').append(tempView.$el);
tempView.render();
});
this.$('.tbody').mCustomScrollbar({
scrollInertia: 0,
});
return this;
}
});
var DataTableRow = Backbone.View.extend({
tag: 'div',
className: 'tr',
initialize: function(opts) {
var that = this;
if(typeof opts.parent !== 'undefined') {
this.parent = opts.parent;
}
if(typeof opts.columns !== 'undefined') {
var temp = {};
that.attributes.columns = _.map(opts.columns, function(col) {
return {
text: that.model.get(col.modelKey),
col: col.col
};
});
}
},
attributes: { columns: [] },
template: function() {
var temp;
$.ajax(root + 'templates/data-table.html', {
success: function(data) {
// console.log(data);
temp = Mustache.compile($(data).filter('#data-table-row-template').html());
},
async: false
});
return temp;
}(),
events: {
},
serialize: function() {
var that = this;
return {
root: root,
columns: that.attributes.columns
};
},
resize: function() {
var that = this;
},
render: function() {
var that = this;
var html = this.template(this.serialize());
this.$el.html(html);
return this;
}
});
I know I could get around it by creating different views for each data table, but this should work and I am at a loss why it isn't. Any help here? Thanks.
EDIT: as an attempt to understand this better, here is the underscore extend function:
_.extend = function(obj) {
each(slice.call(arguments, 1), function(source) {
if (source) {
for (var prop in source) {
obj[prop] = source[prop];
}
}
});
return obj;
};
What makes these attributes attach to the prototype?
Upvotes: 0
Views: 501
Reputation: 161457
Your attributes
{}
is on the prototype, so it will be shared across all instances of the object. You need to have an attributes
object for each instance.
Instead of something like this:
var DataTable = Backbone.View.extend({
attributes: {}
});
You need to create a new object on each initialization:
var DataTable = Backbone.View.extend({
attributes: {},
initialize: function(options){
this.attributes = {};
}
});
As @muistooshort pointed out, you have this problem for your subViews
array and DataTableRow.prototype.attributes
too. Any key-value pairs in the object passed to extend
are placed on the object's prototype, which means that new instances of the object will all share those attributes. That is how all of your functions end up on each instance, but it also means that everything else does too, so it is up to you to make sure that things are initialized properly for each instance.
Backbone.View.extend
is unrelated to _.extend
except that it happens to use it has a helper just like any other code.
_.extend
takes multiple objects and copies their properties onto the first one.Backbone.View.extend
takes a single object, and returns a new constructor function that will construct an object that has the object as its prototype, and that also inherits the prototype of the first constructor that extend
was called on.https://github.com/jashkenas/backbone/blob/master/backbone.js#L1532
So say you had an object like this:
Backbone.View
constructor that when called will create a new object that has Backbone.View.prototype
as its prototype having the standard view methods.
When you do var DataTable = Backbone.View.extend(newproto_obj)
, you now have this:
DataTable
constructor that when called will create a new object that has a prototype with the values from newproto_obj
, and Backbone.View.prototype
as the prototype of that prototype.
This is called the prototype chain, and it is how JavaScript does its inheritance. When you create a new object, it has no properties of its own unless they are set by Backbone in the constructor or initialize
functions. If you attempt to access a property on the object and it is not present, it will look to see if its prototype has a property by that name. Because the prototype object is a single common object shared across instances, modifying it will change every instance.
Upvotes: 3