Backbone.js получает и устанавливает атрибут вложенного объекта

У меня есть простой вопрос о функциях Backbone.js get и set.

1) С помощью приведенного ниже кода, как я могу "получить" или "установить" obj1.myAttribute1 напрямую?

Другой вопрос:

2) В модели, помимо объекта defaults, где можно/должен объявить мою модель других атрибутов, чтобы к ним можно было получить доступ через методы getbbbb и bb?

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

Я знаю, что могу:

this.model.get("obj1").myAttribute1;

но это хорошая практика?

Ответ 1

В то время как this.model.get("obj1").myAttribute1 в порядке, это немного проблематично, потому что тогда у вас может возникнуть соблазн сделать тот же тип вещи для набора, т.е.

this.model.get("obj1").myAttribute1 = true;

Но если вы это сделаете, вы не получите преимущества моделей Backbone для myAttribute1, например, события изменения или проверки.

Лучшим решением было бы никогда не встраивать POJSOs ( "простые старые объекты JavaScript" ) в ваши модели и вместо этого устанавливать собственные классы моделей. Поэтому он выглядит примерно так:

var Obj = Backbone.Model.extend({
    defaults: {
        myAttribute1: false,
        myAttribute2: true
    }
});

var MyModel = Backbone.Model.extend({
    initialize: function () {
        this.set("obj1", new Obj());
    }
});

Тогда код доступа будет

var x = this.model.get("obj1").get("myAttribute1");

но, что более важно, код установки будет

this.model.get("obj1").set({ myAttribute1: true });

который будет запускать соответствующие события изменения и т.п. Пример работы здесь: http://jsfiddle.net/g3U7j/

Ответ 2

Я создал backbone-deep-model для этого - просто расширьте Backbone.DeepModel вместо Backbone.Model, и затем вы можете использовать пути для получения/установки вложенные атрибуты модели. Он также поддерживает события изменения.

model.bind('change:user.name.first', function(){...});
model.set({'user.name.first': 'Eric'});
model.get('user.name.first'); //Eric

Ответ 3

Доменическое решение будет работать, однако каждый новый MyModel укажет на тот же экземпляр Obj. Чтобы избежать этого, MyModel должен выглядеть так:

var MyModel = Backbone.Model.extend({
  initialize: function() {
     myDefaults = {
       obj1: new Obj()
     } 
     this.set(myDefaults);
  }
});

См. c3rin answer @fooobar.com/questions/56309/... для полного объяснения.

Ответ 4

Я использую этот подход.

Если у вас есть модель Backbone, например:

var nestedAttrModel = new Backbone.Model({
    a: {b: 1, c: 2}
});

Вы можете установить атрибут "a.b" с помощью:

var _a = _.omit(nestedAttrModel.get('a')); // from underscore.js
_a.b = 3;
nestedAttrModel.set('a', _a);

Теперь ваша модель будет иметь такие атрибуты, как:

{a: {b: 3, c: 2}}

с событием "change".

Ответ 5

У меня была та же проблема @pagewil и @Benno с решением @Domenic. Мой ответ заключался в том, чтобы вместо этого написать простой подкласс класса Backbone.Model, который исправляет проблему.

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

Что делает NestedModel для вас, это позволяет им работать (что и происходит, когда myModel устанавливается через данные JSON):

var myModel = new MyModel();
myModel.set({ obj1: { myAttribute1: 'abc', myAttribute2: 'xyz' } });
myModel.set('obj1', { myAttribute1: 123, myAttribute2: 456 });

Было бы легко создать список моделей автоматически при инициализации, но это решение было достаточно для меня.

Ответ 6

Решение, предложенное Домеником, имеет некоторые недостатки. Предположим, вы хотите прослушать событие "change". В этом случае метод "initialize" не будет запущен, и ваше пользовательское значение для атрибута будет заменено на json-объект с сервера. В моем проекте я столкнулся с этой проблемой. Мое решение переопределить метод "set" модели:

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

Ответ 7

Есть одно решение, о котором никто еще не подумал, и его можно использовать много. Вы действительно не можете устанавливать вложенные атрибуты напрямую, если вы не используете стороннюю библиотеку, которую вы, вероятно, не хотите. Однако то, что вы можете сделать, это сделать клон оригинального словаря, установить вложенное свойство и установить весь словарь. Кусок торта.

//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 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

Ответ 8

Хотя в некоторых случаях использование моделей Backbone вместо вложенных атрибутов объекта имеет смысл, о чем упоминалось Доменик, в более простых случаях вы могли бы создать функцию setter в модели:

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

Ответ 9

Если вы взаимодействуете с бэкэнд, для которого требуется объект с структурой вложенности. Но с основой легче работать с линейной структурой.

backbone.linear может помочь вам.