Backbone.js mendapatkan dan menyetel atribut objek bersarang

105

Saya punya pertanyaan sederhana tentang get dan set fungsi Backbone.js .

1) Dengan kode di bawah ini, bagaimana saya bisa 'mendapatkan' atau 'menyetel' obj1.myAttribute1 secara langsung?

Pertanyaan lain:

2) Dalam Model, selain dari objek default , di mana saya dapat / harus mendeklarasikan atribut lain dari model saya, sehingga atribut tersebut dapat diakses melalui metode get dan 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?
    }
});

Saya tahu saya bisa melakukan:

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

tetapi apakah itu praktik yang baik?

keberuntungan
sumber
3
Meskipun ini bukan jawaban untuk pertanyaan: Setiap kali menentukan objek (apa pun yang diteruskan oleh referensi) di defaults(obj1 dalam kasus ini), objek yang sama itu akan dibagikan di semua contoh model. Praktik saat ini adalah mendefinisikan defaultssebagai fungsi yang mengembalikan objek untuk digunakan sebagai default. backbonejs.org/#Model-defaults (lihat catatan miring)
Jonathan F
1
@JonathanF Komentar tidak dimaksudkan untuk jawaban sehingga Anda tidak pernah membutuhkan deklarasi :)
TJ

Jawaban:

144

Meskipun this.model.get("obj1").myAttribute1baik-baik saja, ini sedikit bermasalah karena Anda mungkin tergoda untuk melakukan hal yang sama untuk set, yaitu

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

Tetapi jika Anda melakukan ini, Anda tidak akan mendapatkan manfaat dari model Backbone myAttribute1, seperti peristiwa perubahan atau validasi.

Solusi yang lebih baik adalah dengan tidak pernah menumpuk POJSO ("objek JavaScript lama biasa") di model Anda, dan sebagai gantinya menumpuk kelas model khusus. Jadi akan terlihat seperti ini:

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

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

Maka kode aksesnya adalah

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

tetapi yang lebih penting adalah kode pengaturannya

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

yang akan mengaktifkan peristiwa perubahan yang sesuai dan sejenisnya. Contoh kerja di sini: http://jsfiddle.net/g3U7j/

Domenik
sumber
24
Untuk jawaban ini, saya akan menambahkan saran bahwa solusi ini mengganggu pelanggaran Hukum Demeter yang meluas. Saya akan mempertimbangkan untuk menambahkan metode kenyamanan yang menyembunyikan navigasi ke objek bersarang. Pada dasarnya, penelepon Anda tidak perlu mengetahui struktur internal model; bagaimanapun, itu mungkin berubah dan penelepon tidak harus lebih bijaksana.
Bill Eisenhauer
7
Tidak bisa mendapatkan ini bekerja untuk saya. Kesalahan Uncaught TypeError: Object #<Object> has no method 'set'
lemparan
1
@ChristianNunciato, pagewil, Benno: Sepertinya Anda melewatkan inti dari postingan ini, yaitu menyarangkan model Backbone di dalam model Backbone. Jangan menyarangkan objek polos di dalam model Backbone. Contoh kerja di sini: jsfiddle.net/g3U7j
Domenic
1
Saya tidak memeriksa kode backbone.js, tetapi dari pengujian saya, jika Anda memiliki Model kustom bersarang dan mengubah propertinya dengan set (), model induknya tidak akan mengaktifkan peristiwa 'perubahan' itu sendiri; Saya harus memecat acara sendiri. Saya benar-benar harus memeriksa kodenya, tetapi apakah ini pemahaman Anda juga?
tom
2
@tom itu benar. Backbone tidak kasus khusus ketika properti model adalah contoh Backbone.Model, dan kemudian mulai melakukan gelembung peristiwa magis.
Domenic
74

Saya membuat backbone-deep-model untuk ini - cukup perpanjang Backbone.DeepModel daripada Backbone.Model dan Anda kemudian dapat menggunakan jalur untuk mendapatkan / mengatur atribut model bersarang. Ia juga mempertahankan acara perubahan.

model.bind('change:user.name.first', function(){...});
model.set({'user.name.first': 'Eric'});
model.get('user.name.first'); //Eric
evilcelery
sumber
1
Ya, jika Anda melihat API ada contoh seperti//You can use index notation to fetch from arrays console.log(model.get('otherSpies.0.name')) //'Lana'
tawheed
Bekerja dengan baik! Tetapi apakah baris 2 dalam contoh Anda membutuhkan titik dua, bukan koma?
mariachi
16

Solusi Domenic akan berfungsi, namun setiap MyModel baru akan mengarah ke instance Obj yang sama. Untuk menghindari ini, MyModel akan terlihat seperti:

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

Lihat jawaban c3rin @ https://stackoverflow.com/a/6364480/1072653 untuk penjelasan lengkap.

Berkarat
sumber
1
Untuk pembaca selanjutnya, jawaban saya telah diperbarui untuk memasukkan jawaban terbaik Rusty.
Domenic
2
Penanya harus menandai ini sebagai jawaban yang diterima. Domenic's adalah awal yang baik, tetapi ini menyelesaikan masalah.
Jon Raasch
5

Saya menggunakan pendekatan ini.

Jika Anda memiliki model Backbone seperti ini:

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

Anda dapat menyetel atribut "ab" dengan:

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

Sekarang model Anda akan memiliki atribut seperti:

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

dengan peristiwa "ubah" diaktifkan.

Natthakit Susanthitanon
sumber
1
Apa kau yakin tentang ini? ini tidak berhasil untuk saya. meta2= m.get('x'); meta2.id=110; m.set('x', meta2). Ini tidak memicu peristiwa perubahan apa pun untuk saya :(
HungryCoder
1
Saya melihatnya berhasil ketika saya mengkloning atribut suka _.clone(m.get('x')). terima kasih
HungryCoder
Terima kasih @HungryCoder, ini juga berhasil untuk saya saat dikloning. Backbone harus membandingkan objek Anda settingdengan objek Anda gettingpada waktu yang ditentukan. Jadi jika Anda tidak mengkloning kedua objek tersebut, maka kedua objek yang dibandingkan akan sama persis pada waktu yang ditentukan.
Derek Dahmer
Ingatlah bahwa objek diteruskan oleh referensi dan bisa berubah, tidak seperti string dan angka primitif. Metode himpunan dan konstruktor Backbone mencoba klon dangkal dari referensi objek apa pun yang diteruskan sebagai argumen. Referensi apa pun ke objek lain di properti objek itu tidak digandakan. Saat Anda menyetel dan mengambilnya, referensinya sama, yang berarti Anda dapat mengubah model tanpa memicu perubahan.
niall.campbell
3

Ada satu solusi yang belum terpikirkan oleh siapa pun yang banyak digunakan. Anda memang tidak dapat menyetel atribut bertingkat secara langsung, kecuali Anda menggunakan pustaka pihak ketiga yang mungkin tidak Anda inginkan. Namun apa yang dapat Anda lakukan adalah membuat tiruan dari kamus asli, menyetel properti bertingkat di sana dan daripada menyetel seluruh kamus itu. Sepotong kue.

//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
sepeda
sumber
2

Saya memiliki masalah yang sama dengan @pagewil dan @Benno dengan solusi @ Domenic. Jawaban saya adalah untuk menulis sub-kelas sederhana dari Backbone.Model yang memperbaiki masalah.

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

Apa yang dilakukan NestedModel untuk Anda adalah memungkinkan ini berfungsi (yang terjadi ketika myModel disetel melalui data JSON):

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

Mudah untuk membuat daftar model secara otomatis saat menginisialisasi, tetapi solusi ini cukup baik untuk saya.

Cory Gagliardi
sumber
2

Solusi yang diajukan oleh Domenic memiliki beberapa kekurangan. Katakanlah Anda ingin mendengarkan acara 'ubah'. Dalam hal ini, metode 'inisialisasi' tidak akan diaktifkan dan nilai kustom Anda untuk atribut akan diganti dengan objek json dari server. Dalam proyek saya, saya menghadapi masalah ini. Solusi saya untuk mengganti metode 'set' Model:

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

Meskipun dalam beberapa kasus menggunakan model Backbone daripada atribut Objek bersarang masuk akal seperti yang disebutkan Domenic, dalam kasus yang lebih sederhana Anda dapat membuat fungsi penyetel dalam model:

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);
    }
})
Ibrahim Muhammad
sumber
0

Jika Anda berinteraksi dengan backend, yang membutuhkan objek dengan struktur bersarang. Tetapi dengan backbone lebih mudah untuk bekerja dengan struktur linier.

backbone.linear dapat membantu Anda.

orang Negro
sumber