简体   繁体   中英

Backbone View shouldn't have same data in different instances but does

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?

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.

Edit

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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM