Model Bersarang di Backbone.js, cara melakukan pendekatan

117

Saya mendapatkan JSON berikut yang disediakan dari server. Dengan ini, saya ingin membuat model dengan model bersarang. Saya tidak yakin yang mana cara untuk mencapai ini.

//json
[{
    name : "example",
    layout : {
        x : 100,
        y : 100,
    }
}]

Saya ingin ini diubah menjadi dua model tulang punggung bersarang dengan struktur berikut:

// structure
Image
    Layout
...

Jadi saya mendefinisikan model Layout seperti ini:

var Layout = Backbone.Model.extend({});

Tapi manakah dari dua teknik (jika ada) di bawah ini yang harus saya gunakan untuk menentukan model Image? A atau B di bawah?

SEBUAH

var Image = Backbone.Model.extend({
    initialize: function() {
        this.set({ 'layout' : new Layout(this.get('layout')) })
    }
});

atau, B

var Image = Backbone.Model.extend({
    initialize: function() {
        this.layout = new Layout( this.get('layout') );
    }
});
Ross
sumber

Jawaban:

98

Saya memiliki masalah yang sama saat saya menulis aplikasi Backbone saya. Harus berurusan dengan model yang disematkan / bersarang. Saya melakukan beberapa penyesuaian yang menurut saya merupakan solusi yang cukup elegan.

Ya, Anda dapat memodifikasi metode parse untuk mengubah atribut di sekitar objek, tetapi semua itu sebenarnya adalah kode IMO yang tidak dapat dipertahankan, dan terasa lebih seperti peretasan daripada solusi.

Inilah yang saya sarankan untuk contoh Anda:

Pertama tentukan Model Tata Letak Anda seperti itu.

var layoutModel = Backbone.Model.extend({});

Lalu inilah Model gambar Anda:

var imageModel = Backbone.Model.extend({

    model: {
        layout: layoutModel,
    },

    parse: function(response){
        for(var key in this.model)
        {
            var embeddedClass = this.model[key];
            var embeddedData = response[key];
            response[key] = new embeddedClass(embeddedData, {parse:true});
        }
        return response;
    }
});

Perhatikan bahwa saya belum merusak model itu sendiri, tetapi hanya meneruskan objek yang diinginkan dari metode parse.

Ini harus memastikan struktur model bersarang saat Anda membaca dari server. Sekarang, Anda akan melihat bahwa penyimpanan atau pengaturan sebenarnya tidak ditangani di sini karena menurut saya masuk akal bagi Anda untuk mengatur model bersarang secara eksplisit menggunakan model yang tepat.

Seperti:

image.set({layout : new Layout({x: 100, y: 100})})

Perhatikan juga bahwa Anda sebenarnya memanggil metode parse dalam model bersarang Anda dengan memanggil:

new embeddedClass(embeddedData, {parse:true});

Anda dapat menentukan model bersarang modelsebanyak yang Anda butuhkan.

Tentu saja, jika Anda ingin menyimpan model bersarang di tabelnya sendiri. Ini tidak cukup. Tetapi dalam kasus membaca dan menyimpan objek secara keseluruhan, solusi ini sudah cukup.

rycfung.dll
sumber
4
Ini bagus .. harus menjadi jawaban yang diterima karena jauh lebih bersih daripada pendekatan lainnya. Satu-satunya saran yang saya miliki adalah memanfaatkan huruf pertama dari kelas Anda yang memperluas Backbone.Model agar mudah dibaca .. yaitu ImageModel dan LayoutModel
Stephen Handley
1
@StephenHandley Terima kasih atas komentar dan saran Anda. Sebagai informasi, saya sebenarnya menggunakan ini dalam konteks requireJS. Jadi, untuk menjawab masalah kapitalisasi, var 'imageModel' sebenarnya dikembalikan ke requireJS. Dan referensi ke model akan diringkas oleh konstruksi berikut: define(['modelFile'], function(MyModel){... do something with MyModel}) Tapi Anda benar. Saya membiasakan diri untuk mereferensikan model dengan konvensi yang Anda sarankan.
rycfung
@BobS Maaf, ada kesalahan ketik. Seharusnya responnya. Saya sudah memperbaikinya, terima kasih sudah menunjukkan.
rycfung
2
Bagus! Saya merekomendasikan menambahkan ini ke Backbone.Model.prototype.parsefungsi. Kemudian, semua model Anda harus lakukan adalah menentukan jenis objek submodel (dalam atribut "model" Anda).
jasop
1
Keren! Saya akhirnya melakukan sesuatu yang serupa (terutama dan sayangnya setelah saya menemukan jawaban ini) dan menulisnya di sini: blog.untrod.com/2013/08/declarative-approach-to-nesting.html Perbedaan besar adalah untuk model yang sangat bertingkat Saya mendeklarasikan seluruh pemetaan sekaligus, dalam model root / induk, dan kode mengambilnya dari sana dan menelusuri seluruh model, menghidrasi objek yang relevan ke dalam koleksi dan model Backbone. Tapi sebenarnya pendekatannya sangat mirip.
Chris Clark
16

Saya memposting kode ini sebagai contoh saran Peter Lyon untuk mendefinisikan ulang parse. Saya memiliki pertanyaan yang sama dan ini berhasil untuk saya (dengan backend Rails). Kode ini ditulis dalam Coffeescript. Saya membuat beberapa hal eksplisit untuk orang yang tidak terbiasa dengannya.

class AppName.Collections.PostsCollection extends Backbone.Collection
  model: AppName.Models.Post

  url: '/posts'

  ...

  # parse: redefined to allow for nested models
  parse: (response) ->  # function definition
     # convert each comment attribute into a CommentsCollection
    if _.isArray response
      _.each response, (obj) ->
        obj.comments = new AppName.Collections.CommentsCollection obj.comments
    else
      response.comments = new AppName.Collections.CommentsCollection response.comments

    return response

atau, di JS

parse: function(response) {
  if (_.isArray(response)) {
    return _.each(response, function(obj) {
      return obj.comments = new AppName.Collections.CommentsCollection(obj.comments);
    });
  } else {
    response.comments = new AppName.Collections.CommentsCollection(response.comments);
  }
  return response;
};
Eric Hu
sumber
Properti untuk kode contoh dan menyarankan parse utama. Terima kasih!
Edward Anderson
11
akan menyenangkan untuk mendapatkan jawaban Anda dalam JS yang sebenarnya
Jason
6
senang memiliki versi coffeescript, terima kasih. Untuk yang lain, coba js2coffee.org
ABCD.ca
16
Jika pertanyaannya adalah JS nyata, jawabannya juga harus.
Manuel Hernandez
12

Gunakan Backbone.AssociatedModeldari Backbone-association :

    var Layout = Backbone.AssociatedModel.extend({
        defaults : {
            x : 0,
            y : 0
        }
    });
    var Image = Backbone.AssociatedModel.extend({
        relations : [
            type: Backbone.One,
            key : 'layout',
            relatedModel : Layout          
        ],
        defaults : {
            name : '',
            layout : null
        }
    });
Jaynti Kanani
sumber
Solusi bagus. Ada juga proyek serupa di luar sana: github.com/PaulUithol/Backbone-relational
michaelok
11

Saya tidak yakin Backbone sendiri memiliki cara yang direkomendasikan untuk melakukan ini. Apakah objek Layout memiliki ID dan rekamannya sendiri di database back end? Jika demikian Anda dapat membuatnya Model sendiri seperti yang Anda miliki. Jika tidak, Anda bisa membiarkannya sebagai dokumen bersarang, pastikan Anda mengonversinya ke dan dari JSON dengan benar di metode savedan parse. Jika Anda akhirnya mengambil pendekatan seperti ini, saya pikir contoh A Anda lebih konsisten dengan tulang punggung karena setakan diperbarui dengan benar attributes, tetapi sekali lagi saya tidak yakin apa yang dilakukan Backbone dengan model bersarang secara default. Sepertinya Anda memerlukan beberapa kode khusus untuk menangani ini.

Peter Lyons
sumber
Ah! Maaf, newoperatornya hilang . Saya telah mengeditnya untuk memperbaiki kesalahan ini.
Ross
Oh, kemudian saya salah menafsirkan pertanyaan Anda. Saya akan memperbarui jawaban saya.
Peter Lyons
8

Saya akan memilih Opsi B jika Anda ingin membuat semuanya tetap sederhana.

Pilihan bagus lainnya adalah menggunakan Backbone-Relational . Anda baru saja mendefinisikan sesuatu seperti:

var Image = Backbone.Model.extend({
    relations: [
        {
            type: Backbone.HasOne,
            key: 'layout',
            relatedModel: 'Layout'
        }
    ]
});
philfreo.dll
sumber
+1 Backbone-Releational tampaknya cukup mapan: situs web sendiri, bintang 1,6k, 200+ garpu.
Ross
6

Saya menggunakan plugin Backbone DeepModel untuk model dan atribut bersarang.

https://github.com/powmedia/backbone-deep-model

Anda dapat mengikat untuk mengubah level n acara secara mendalam. sebagai contoh: model.on('change:example.nestedmodel.attribute', this.myFunction);

Menandai
sumber
5

Versi CoffeeScript dari jawaban indah rycfung :

class ImageModel extends Backbone.Model
  model: {
      layout: LayoutModel
  }

  parse: (response) =>
    for propName,propModel of @model
      response[propName] = new propModel( response[propName], {parse:true, parentModel:this} )

    return response

Bukankah itu manis? ;)

Dan Fox
sumber
11
Saya tidak mengambil gula dalam JavaScript saya :)
Ross
2

Saya memiliki masalah yang sama dan saya telah bereksperimen dengan kode dalam jawaban rycfung , yang merupakan saran yang bagus.
Namun, jika Anda tidak ingin setmodel bersarang langsung, atau tidak ingin terus-menerus lulus {parse: true}dalam options, pendekatan lain akan mendefinisikan kembali setitu sendiri.

Dalam Backbone 1.0.0 , setyang disebut dalam constructor, unset, clear, fetchdan save.

Pertimbangkan model super berikut , untuk semua model yang membutuhkan model bersarang dan / atau koleksi.

/** Compound supermodel */
var CompoundModel = Backbone.Model.extend({
    /** Override with: key = attribute, value = Model / Collection */
    model: {},

    /** Override default setter, to create nested models. */
    set: function(key, val, options) {
        var attrs, prev;
        if (key == null) { return this; }

        // Handle both `"key", value` and `{key: value}` -style arguments.
        if (typeof key === 'object') {
            attrs = key;
            options = val;
        } else {
            (attrs = {})[key] = val;
        }

        // Run validation.
        if (options) { options.validate = true; }
        else { options = { validate: true }; }

        // For each `set` attribute, apply the respective nested model.
        if (!options.unset) {
            for (key in attrs) {
                if (key in this.model) {
                    if (!(attrs[key] instanceof this.model[key])) {
                        attrs[key] = new this.model[key](attrs[key]);
                    }
                }
            }
        }

        Backbone.Model.prototype.set.call(this, attrs, options);

        if (!(attrs = this.changedAttributes())) { return this; }

        // Bind new nested models and unbind previous nested models.
        for (key in attrs) {
            if (key in this.model) {
                if (prev = this.previous(key)) {
                    this._unsetModel(key, prev);
                }
                if (!options.unset) {
                    this._setModel(key, attrs[key]);
                }
            }
        }
        return this;
    },

    /** Callback for `set` nested models.
     *  Receives:
     *      (String) key: the key on which the model is `set`.
     *      (Object) model: the `set` nested model.
     */
    _setModel: function (key, model) {},

    /** Callback for `unset` nested models.
     *  Receives:
     *      (String) key: the key on which the model is `unset`.
     *      (Object) model: the `unset` nested model.
     */
    _unsetModel: function (key, model) {}
});

Perhatikan bahwa model, _setModeldan _unsetModelsengaja dikosongkan. Pada level abstraksi ini, Anda mungkin tidak dapat menentukan tindakan yang wajar untuk callback. Namun, Anda mungkin ingin menimpanya di submodel yang diperluas CompoundModel.
Callback tersebut berguna, misalnya, untuk mengikat listener dan menyebarkan changeperistiwa.


Contoh:

var Layout = Backbone.Model.extend({});

var Image = CompoundModel.extend({
    defaults: function () {
        return {
            name: "example",
            layout: { x: 0, y: 0 }
        };
    },

    /** We need to override this, to define the nested model. */
    model: { layout: Layout },

    initialize: function () {
        _.bindAll(this, "_propagateChange");
    },

    /** Callback to propagate "change" events. */
    _propagateChange: function () {
        this.trigger("change:layout", this, this.get("layout"), null);
        this.trigger("change", this, null);
    },

    /** We override this callback to bind the listener.
     *  This is called when a Layout is set.
     */
    _setModel: function (key, model) {
        if (key !== "layout") { return false; }
        this.listenTo(model, "change", this._propagateChange);
    },

    /** We override this callback to unbind the listener.
     *  This is called when a Layout is unset, or overwritten.
     */
    _unsetModel: function (key, model) {
        if (key !== "layout") { return false; }
        this.stopListening();
    }
});

Dengan ini, Anda memiliki pembuatan model bertingkat otomatis dan penyebaran peristiwa. Penggunaan sampel juga disediakan dan diuji:

function logStringified (obj) {
    console.log(JSON.stringify(obj));
}

// Create an image with the default attributes.
// Note that a Layout model is created too,
// since we have a default value for "layout".
var img = new Image();
logStringified(img);

// Log the image everytime a "change" is fired.
img.on("change", logStringified);

// Creates the nested model with the given attributes.
img.set("layout", { x: 100, y: 100 });

// Writing on the layout propagates "change" to the image.
// This makes the image also fire a "change", because of `_propagateChange`.
img.get("layout").set("x", 50);

// You may also set model instances yourself.
img.set("layout", new Layout({ x: 100, y: 100 }));

Keluaran:

{"name":"example","layout":{"x":0,"y":0}}
{"name":"example","layout":{"x":100,"y":100}}
{"name":"example","layout":{"x":50,"y":100}}
{"name":"example","layout":{"x":100,"y":100}}
afsantos.dll
sumber
2

Saya menyadari saya terlambat ke pesta ini, tetapi kami baru-baru ini merilis plugin untuk menangani skenario ini dengan tepat. Ini disebut backbone-nestify .

Jadi model bertingkat Anda tetap tidak berubah:

var Layout = Backbone.Model.extend({...});

Kemudian gunakan plugin saat menentukan model yang memuat (menggunakan Underscore.extend ):

var spec = {
    layout: Layout
};
var Image = Backbone.Model.extend(_.extend({
    // ...
}, nestify(spec));

Setelah itu, dengan asumsi Anda memiliki model myang merupakan turunan dari Image, dan Anda telah menyetel JSON dari pertanyaan m, Anda dapat melakukan:

m.get("layout");    //returns the nested instance of Layout
m.get("layout|x");  //returns 100
m.set("layout|x", 50);
m.get("layout|x");  //returns 50
Scott Bale
sumber
2

Gunakan bentuk tulang punggung

Ini mendukung formulir, model, dan toJSON bersarang. SEMUA BERLARANG

var Address = Backbone.Model.extend({
    schema: {
    street:  'Text'
    },

    defaults: {
    street: "Arteaga"
    }

});


var User = Backbone.Model.extend({
    schema: {
    title:      { type: 'Select', options: ['Mr', 'Mrs', 'Ms'] },
    name:       'Text',
    email:      { validators: ['required', 'email'] },
    birthday:   'Date',
    password:   'Password',
    address:    { type: 'NestedModel', model: Address },
    notes:      { type: 'List', itemType: 'Text' }
    },

    constructor: function(){
    Backbone.Model.apply(this, arguments);
    },

    defaults: {
    email: "[email protected]"
    }
});

var user = new User();

user.set({address: {street: "my other street"}});

console.log(user.toJSON()["address"]["street"])
//=> my other street

var form = new Backbone.Form({
    model: user
}).render();

$('body').append(form.el);
David Rz Ayala
sumber
1

Jika Anda tidak ingin menambahkan belum kerangka lain, Anda mungkin mempertimbangkan untuk membuat kelas dasar dengan ditimpa setdan toJSONdan menggunakannya seperti ini:

// Declaration

window.app.viewer.Model.GallerySection = window.app.Model.BaseModel.extend({
  nestedTypes: {
    background: window.app.viewer.Model.Image,
    images: window.app.viewer.Collection.MediaCollection
  }
});

// Usage

var gallery = new window.app.viewer.Model.GallerySection({
    background: { url: 'http://example.com/example.jpg' },
    images: [
        { url: 'http://example.com/1.jpg' },
        { url: 'http://example.com/2.jpg' },
        { url: 'http://example.com/3.jpg' }
    ],
    title: 'Wow'
}); // (fetch will work equally well)

console.log(gallery.get('background')); // window.app.viewer.Model.Image
console.log(gallery.get('images')); // window.app.viewer.Collection.MediaCollection
console.log(gallery.get('title')); // plain string

Anda akan membutuhkan BaseModeljawaban ini (tersedia, jika Anda suka, sebagai intinya ).

Dan Abramov
sumber
1

Kami juga mengalami masalah ini dan pekerja tim telah mengimplementasikan plugin bernama backbone-nested-atribut.

Penggunaannya sangat sederhana. Contoh:

var Tree = Backbone.Model.extend({
  relations: [
    {
      key: 'fruits',
      relatedModel: function () { return Fruit }
    }
  ]
})

var Fruit = Backbone.Model.extend({
})

Dengan ini, model Pohon dapat mengakses buah-buahan:

tree.get('fruits')

Anda dapat melihat lebih banyak informasi di sini:

https://github.com/dtmtec/backbone-nested-attributes

Gustavo Kloh
sumber