Backbone.js'de yuvalanmış modeller, nasıl yaklaşılır


117

Bir sunucudan sağlanan aşağıdaki JSON'yi aldım. Bununla iç içe bir modele sahip bir model oluşturmak istiyorum. Bunu başarmanın yolunun hangisi olduğundan emin değilim.

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

Bunların aşağıdaki yapıya sahip iki iç içe geçmiş omurga modeline dönüştürülmesini istiyorum:

// structure
Image
    Layout
...

Bu yüzden Layout modelini şu şekilde tanımlıyorum:

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

Ancak Görüntü modelini tanımlamak için aşağıdaki iki (varsa) teknikten hangisini kullanmalıyım? Aşağıda A veya B?

bir

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

veya B

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

Yanıtlar:


98

Backbone uygulamamı yazarken de aynı sorunu yaşıyorum. Gömülü / iç içe modellerle uğraşmak zorunda. Oldukça zarif bir çözüm olduğunu düşündüğüm bazı ince ayarlar yaptım.

Evet, nesnenin etrafındaki bir özniteliği değiştirmek için ayrıştırma yöntemini değiştirebilirsiniz, ancak bunların tümü aslında oldukça ele alınamaz bir kod IMO'dur ve bir çözümden çok bir bilgisayar korsanlığı hissi verir.

İşte örneğiniz için önerdiğim şey:

Öncelikle Yerleşim Modelinizi bu şekilde tanımlayın.

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

O zaman işte görüntünüz Model:

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

Modelin kendisini kurcalamadığıma, sadece istenen nesneyi ayrıştırma yönteminden geri verdiğime dikkat edin.

Bu, sunucudan okurken yuvalanmış modelin yapısını sağlamalıdır. Şimdi, kaydetme veya ayarlamanın aslında burada ele alınmadığını fark edeceksiniz çünkü iç içe modeli uygun modeli kullanarak açıkça ayarlamanın sizin için mantıklı olduğunu düşünüyorum.

Şöyle:

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

Ayrıca, aslında iç içe modelinizdeki ayrıştırma yöntemini çağırdığınızı unutmayın:

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

modelAlanda ihtiyaç duyduğunuz kadar iç içe model tanımlayabilirsiniz .

Elbette, iç içe yerleştirilmiş modeli kendi tablosuna kaydedecek kadar ileri gitmek istiyorsanız. Bu yeterli olmaz. Ancak nesnenin bir bütün olarak okunması ve kaydedilmesi durumunda bu çözüm yeterli olacaktır.


4
Bu güzel .. diğer yaklaşımlardan çok daha temiz olduğu için kabul edilen cevap olmalıdır. Okunabilirlik için Backbone.Model'i genişleten sınıflarınızın ilk harfini büyük harfle yazmak istiyorum. ImageModel ve LayoutModel
Stephen Handley

1
@StephenHandley Yorumunuz ve öneriniz için teşekkürler. Bilgi için, aslında bunu requireJS bağlamında kullanıyorum. Dolayısıyla, büyük harf kullanımı konusunu yanıtlamak için, var 'imageModel' aslında requiredJS'ye döndürülür. Ve modele yapılan referans aşağıdaki yapı tarafından özetlenecektir: define(['modelFile'], function(MyModel){... do something with MyModel}) Ama haklısın. Modeli önerdiğiniz kongreye göre referans göstermeyi alışkanlık haline getiriyorum.
rycfung

@BobS Üzgünüm, bir yazım hatası. Cevap olmalıydı. Düzelttim, işaret ettiğin için teşekkürler.
rycfung

2
Güzel! Bunu Backbone.Model.prototype.parseişleve eklemenizi tavsiye ederim . Ardından, modelinizin yapması gereken tek şey, alt model nesne türlerini tanımlamaktır ("model" özelliğinizde).
jasop

1
Güzel! Benzer bir şey yaptım (özellikle ve maalesef bu cevabı bulduktan sonra) ve buraya yazdım: blog.untrod.com/2013/08/declarative-approach-to-nesting.html En büyük fark, derinlemesine iç içe geçmiş modeller için Tüm eşlemeyi tek seferde kök / üst modelde beyan ederim ve kod onu oradan alır ve tüm model boyunca ilerler, ilgili nesneleri Backbone koleksiyonlarına ve modellerine nemlendirir. Ama gerçekten çok benzer bir yaklaşım.
Chris Clark 13

16

Bu kodu, Peter Lyon'un ayrıştırmayı yeniden tanımlama önerisine bir örnek olarak gönderiyorum. Aynı soruyu sordum ve bu benim için çalıştı (Rails arka uç ile). Bu kod Coffeescript ile yazılmıştır. Aşina olmayan insanlar için birkaç şeyi açıklığa kavuşturdum.

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

veya JS'de

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

Örnek kod için aksesuarlar ve geçersiz kılınan ayrıştırmayı önerir. Teşekkürler!
Edward Anderson

11
cevabınızı gerçek JS ile almak güzel olurdu
Jason

6
kahve yazı versiyonuna sahip olduğum için mutluyum, teşekkürler. Diğerleri için js2coffee.org
ABCD.ca'yı

16
Soru gerçek JS ise, bir cevap da olmalıdır.
Manuel Hernandez


11

Backbone'un bunu yapmak için önerilen bir yolu olduğundan emin değilim. Layout nesnesinin arka uç veritabanında kendi kimliği ve kaydı var mı? Eğer öyleyse, sahip olduğunuz gibi kendi Modelini yapabilirsiniz. Değilse, onu yuvalanmış bir belge olarak bırakabilirsiniz, sadece saveve parseyöntemlerinde onu JSON'a ve JSON'dan doğru şekilde dönüştürdüğünüzden emin olun . Böyle bir yaklaşımla sonuçlanırsanız, A örneğinizin omurga ile daha tutarlı olduğunu düşünüyorum, çünkü setdüzgün bir şekilde güncellenecektir attributes, ancak yine Backbone'un varsayılan olarak yuvalanmış modellerle ne yaptığından emin değilim. Bunun üstesinden gelmek için muhtemelen bazı özel koda ihtiyacınız olacak.


Ah! Üzgünüz, newoperatör eksikti . Bu hatayı düzeltmek için düzenledim.
Ross

Oh, o zaman sorunuzu yanlış yorumladım. Cevabımı güncelleyeceğim.
Peter Lyons

8

İşleri basit tutmak istiyorsanız B Seçeneği ile giderim.

Diğer bir iyi seçenek ise Backbone-Relational kullanmaktır . Sadece şöyle bir şey tanımlarsınız:

var Image = Backbone.Model.extend({
    relations: [
        {
            type: Backbone.HasOne,
            key: 'layout',
            relatedModel: 'Layout'
        }
    ]
});

+1 Backbone-Releaseational oldukça sağlam görünüyor: kendi web sitesi, 1.6k yıldız, 200+ çatal.
Ross

6

İç içe geçmiş modeller ve öznitelikler için Backbone DeepModel eklentisini kullanıyorum.

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

Olayların derinliklerini değiştirmek için bağlanabilirsiniz. Örneğin: model.on('change:example.nestedmodel.attribute', this.myFunction);


5

Arasında CoffeeScript versiyonu rycfung var güzel cevap:

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

Bu tatlı değil mi? ;)


11
JavaScript'imde şeker almıyorum
Ross

2

Aynı sorunu yaşadım ve harika bir öneri olan rycfung'un cevabındaki kodla deneyler yapıyorum .
Ancak, istemediğiniz takdirde setdoğrudan iç içe modelleri veya sürekli geçmek istemiyorum {parse: true}içinde options, başka bir yaklaşım yeniden tanımlamak olacaktır setkendisini.

Gelen Backbone 1.0.0 , setdenir constructor, unset, clear, fetchve save.

Modelleri ve / veya koleksiyonları iç içe yerleştirmesi gereken tüm modeller için aşağıdaki süper modeli düşünün .

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

Bildirim o model, _setModelve _unsetModelbilerek boş bırakılır. Bu soyutlama düzeyinde, geri aramalar için muhtemelen makul eylemler tanımlayamazsınız. Ancak, genişleyen alt modellerde bunları geçersiz kılmak isteyebilirsiniz CompoundModel.
Bu geri aramalar, örneğin dinleyicileri bağlamak ve changeolayları yaymak için kullanışlıdır .


Misal:

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

Bununla, otomatik iç içe model oluşturma ve olay yaymaya sahip olursunuz. Örnek kullanım da sağlanır ve test edilir:

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

Çıktı:

{"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}}

2

Bu partiye geç kaldığımı anlıyorum, ancak kısa süre önce tam olarak bu senaryoyu ele almak için bir eklenti yayınladık. Deniyor omurga-nestify .

Böylece iç içe geçmiş modeliniz değişmeden kalır:

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

Ardından, içeren modeli tanımlarken eklentiyi kullanın ( Underscore.extend kullanarak ):

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

Bundan sonra, mörneği olan bir modeliniz olduğunu Imageve sorudan JSON'u ayarladığınızı varsayarsak m, şunları yapabilirsiniz:

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

2

Omurga formlarını kullanın

Yuvalanmış formları, modelleri ve toJSON'u destekler. HEPSİ İÇİNDE

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: "x@x.com"
    }
});

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

1

Eğer henüz başka çerçeve eklemek istemiyorsanız, geçersiz kılınmış bir temel sınıf oluşturmayı düşünebilirsiniz setve toJSONve bu gibi kullanmak:

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

BaseModelBu yanıttan ihtiyacınız olacak (isterseniz, özet olarak mevcuttur ).


1

Biz de bu sorunu yaşıyoruz ve bir ekip çalışanı omurga iç içe özellikler adlı bir eklenti uyguladı.

Kullanımı çok basit. Misal:

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

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

Bununla, Ağaç modeli daha sonra meyvelere erişebilir:

tree.get('fruits')

Daha fazla bilgiyi burada görebilirsiniz:

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

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.