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

105

У меня простой вопрос о функциях получения и установки Backbone.js .

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

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

2) В модели, помимо объекта по умолчанию , где я могу / должен объявить другие атрибуты моей модели, чтобы к ним можно было получить доступ через методы get и set Backbone?

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;

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

удачаРис
источник
3
Хотя это не ответ на вопрос: всякий раз, когда указывается объект (что-либо, переданное по ссылке) в defaults(obj1 в данном случае), этот же объект будет использоваться всеми экземплярами модели. Текущая практика состоит в том, чтобы определить defaultsкак функцию, которая возвращает объект, который будет использоваться по умолчанию. backbonejs.org/#Model-defaults (см. примечание, выделенное курсивом)
Джонатан Ф
1
@JonathanF Комментарии не предназначены для ответов, поэтому декларация вам никогда не нужна :)
TJ

Ответы:

144

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

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

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

Лучшим решением было бы никогда не вкладывать POJSO («простые старые объекты 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/

Доменик
источник
24
К этому ответу я бы добавил совет о том, что это решение балансирует на широко распространенных нарушениях Закона Деметры. Я бы подумал о добавлении удобных методов, которые скрывают навигацию к вложенному объекту. По сути, вызывающим абонентам не нужно знать внутреннюю структуру модели; в конце концов, это может измениться, и звонящие не должны быть мудрее.
Bill Eisenhauer
7
Не могу заставить это работать для меня. Выдает ошибку:Uncaught TypeError: Object #<Object> has no method 'set'
wilsonpage
1
@ChristianNunciato, pagewil, Бенно: Вы, кажется, упустили суть поста, которая заключается в том, чтобы встраивать модели Backbone в модели Backbone. Не вкладывайте простые объекты в модели Backbone. Рабочий пример здесь: jsfiddle.net/g3U7j
Доменик
1
Я не проверял код backbone.js, но, судя по моему тесту, если у вас есть вложенная настраиваемая модель и вы измените ее свойство с помощью set (), ее родительская модель сама не вызовет событие «изменение»; Мне пришлось самому запустить мероприятие. Мне действительно нужно просто проверить код, но это тоже ваше понимание?
Tom
2
@tom, это правильно. Backbone не является частным случаем, когда свойства моделей являются экземплярами Backbone.Model, а затем начинают волшебное всплытие событий.
Domenic
74

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

model.bind('change:user.name.first', function(){...});
model.set({'user.name.first': 'Eric'});
model.get('user.name.first'); //Eric
злой сельдерей
источник
1
Да, это так, если вы посмотрите на API, есть такой пример//You can use index notation to fetch from arrays console.log(model.get('otherSpies.0.name')) //'Lana'
tawheed
Прекрасно работает! Но требуется ли в строке 2 в вашем примере двоеточие вместо запятой?
mariachi
16

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

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

См. Ответ c3rin @ https://stackoverflow.com/a/6364480/1072653 для полного объяснения.

Расти
источник
1
Для будущих читателей мой ответ был обновлен, чтобы включить лучшее из ответа Расти.
Доменик
2
Спрашивающий должен отметить это как принятый ответ. Доменика - отличное начало, но это решает проблему.
Jon Raasch
5

Я использую такой подход.

Если у вас есть такая модель Backbone:

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

Вы можете установить атрибут «ab» с помощью:

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

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

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

с запущенным событием "изменение".

Наттхакит Сусантитанон
источник
1
вы уверены в этом? это не работает для меня. meta2= m.get('x'); meta2.id=110; m.set('x', meta2). Для меня это не вызывает никаких изменений :(
HungryCoder
1
Я вижу, что это работает, когда я клонирую атрибут вроде _.clone(m.get('x')). спасибо
HungryCoder
Спасибо @HungryCoder, у меня тоже сработало при клонировании. Backbone должен сравнивать объект, которым вы являетесь, settingс объектом, которым вы являетесь gettingв заданное время. Таким образом, если вы не клонируете два объекта, тогда два сравниваемых объекта будут точно такими же в установленное время.
Дерек Дамер,
Помните, что объекты передаются по ссылке и являются изменяемыми, в отличие от строковых и числовых примитивов. Методы набора и конструктора Backbone пытаются неглубоко клонировать любую ссылку на объект, переданную в качестве аргумента. Любые ссылки на другие объекты в свойствах этого объекта не клонируются. Когда вы устанавливаете и извлекаете ее, ссылка остается той же, что означает, что вы можете изменять модель, не вызывая изменений.
niall.campbell
3

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

//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
велосипед
источник
2

У меня была та же проблема, что и у @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 });

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

Кори Гальярди
источник
2

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

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);
}, 
Юлисков
источник
0

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

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);
    }
})
Ибрагим Мухаммад
источник
0

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

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

темный
источник