Backbone.js отримує та встановлює вкладений атрибут об'єкта


105

У мене є просте запитання щодо функцій отримання та встановлення Backbone.js .

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

Інше питання:

2) У Моделі, окрім об'єкта за замовчуванням , де я можу / чи повинен оголосити інші атрибути моєї моделі, щоб вони могли отримати доступ через методи отримання та встановлення 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 (див. курсивну примітку)
Jonathan F

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
До цієї відповіді я додам рекомендацію, що це рішення має відношення до поширених порушень Закону про Демет. Я б розглядав можливість додавання зручних методів, які приховують навігацію до вкладеного об'єкта. По суті, вашим абонентам не потрібно знати внутрішню структуру моделі; зрештою, це може змінитися, і абоненти не повинні бути мудрішими.
Білл Ейзенхауер

7
Не можу, щоб це працювало на мене. Помилка кидає:Uncaught TypeError: Object #<Object> has no method 'set'
wilsonpage

1
@ChristianNunciato, pagewil, Benno: Ви, здається, пропустили точку посту, яка полягає в гніздові моделі Backbone всередині моделей Backbone. Не вкладайте звичайні предмети в моделі хребта. Робота тут приклад: jsfiddle.net/g3U7j
Доменік

1
Я не перевірив код backbone.js, але з мого тесту, якщо у вас є вкладена спеціальна модель та змінено властивість її за допомогою set (), її батьківська модель не запустить подія "зміни" сама; Мені довелося самому пожежу події. Я дійсно повинен просто перевірити код, але чи це теж ваше розуміння?
tom

2
@tom це правильно. Хребта не є особливим випадком, коли властивості моделей є екземплярами Backbone.Model, а потім починають робити магічні події.
Доменік

74

Я створив для цього модель-глибоку модель - просто розгорніть 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

Рішення Domenic буде працювати, проте кожен новий 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
Запитувач повинен позначити це як прийняту відповідь. Доменік - це чудовий початок, але це вирішує проблему.
Джон Рааш

5

Я використовую такий підхід.

Якщо у вас є така модель хребта:

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, він працював і на мене, коли клонували. Магістральна система повинна порівнювати об'єкт, який ви знаходитесь, 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" моделі:

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 замість вкладених атрибутів Object має сенс, як згадував Доменік, у більш простих випадках ви можете створити функцію сеттера в моделі:

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 може вам допомогти.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.