Backbone.jsのネストされたモデル、アプローチ方法


117

サーバーから次のJSONを取得しました。これで、ネストされたモデルでモデルを作成したいと思います。これを達成する方法がどちらなのかはわかりません。

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

これらを次の構造を持つ2つのネストされたバックボーンモデルに変換してください。

// structure
Image
    Layout
...

したがって、レイアウトモデルを次のように定義します。

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

しかし、画像モデルを定義するために以下の2つの技術(ある場合)のどちらを使用する必要がありますか?以下のAまたはB?

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

または、 B

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

回答:


98

Backboneアプリケーションを作成しているときにも同じ問題が発生します。埋め込み/ネストされたモデルを処理する必要があります。私はかなりエレガントな解決策だと思ったいくつかの調整を行いました。

はい、解析メソッドを変更してオブジェクト内の属性を変更することができますが、実際にはそれらはすべて保守不可能なコードIMOであり、ソリューションというよりはハックのように感じられます。

これがあなたの例に私が提案するものです:

まず、レイアウトモデルを次のように定義します。

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

次に、ここにあなたの画像モデルがあります:

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

モデル自体を改ざんしていないが、解析メソッドから目的のオブジェクトを単に返しているだけであることに注意してください。

これにより、サーバーからの読み取り時に、ネストされたモデルの構造が保証されます。ここで、適切なモデルを使用してネストされたモデルを明示的に設定することは理にかなっていると思うので、保存または設定は実際にはここでは処理されません。

そのようです:

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

また、ネストされたモデルのparseメソッドを実際に呼び出していることにも注意してください。

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

modelフィールドには、必要なだけネストされたモデルを定義できます。

もちろん、ネストされたモデルを独自のテーブルに保存することもできます。これでは十分ではありません。ただし、オブジェクト全体を読み取って保存する場合は、このソリューションで十分です。


4
これはいいです。他のアプローチよりもはるかにクリーンであるため、受け入れられる答えになるはずです。私が持っている提案は、Backbone.Modelを拡張するクラスの最初の文字を読みやすくするために大文字にすることです。つまり、ImageModelとLayoutModel
Stephen Handley

1
@StephenHandleyコメントと提案をありがとう。詳細については、requireJSのコンテキストで実際に使用しています。したがって、大文字の問題に答えるために、var 'imageModel'は実際にはrequireJSに返されます。そして、モデルへの参照は次の構成要素によってカプセル化され define(['modelFile'], function(MyModel){... do something with MyModel}) ます。しかし、あなたは正しいです。私はあなたが提案した慣習でモデルを参照することを習慣にしています。
rycfung

@BobS申し訳ありませんが、タイプミスでした。応答する必要があります。指摘してくれてありがとう。
rycfung

2
いいね!これをBackbone.Model.prototype.parse関数に追加することをお勧めします。次に、モデルがしなければならないことは、(「モデル」属性で)サブモデルオブジェクトタイプを定義することだけです。
jasop 2013年

1
涼しい!私は似たようなことをして(特に、この答えを見つけた後は残念に思います)、ここに書きました:blog.untrod.com/2013/08/declarative-approach-to-nesting.html大きな違いは、深くネストされたモデルの場合ですルート/親モデルで、マッピング全体を一度に宣言します。コードはそこから取得し、モデル全体をたどり、関連するオブジェクトをバックボーンコレクションとモデルにハイドレートします。しかし、実際には非常によく似たアプローチです。
クリスクラーク

16

このコードは、解析を再定義するというピーターリヨンの提案の例として投稿しています。私は同じ質問をしました、そしてこれは私のために(Railsバックエンドで)うまくいきました。このコードはCoffeescriptで書かれています。慣れていない人のために、いくつかのことを明示しました。

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

または、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;
};

コード例の提案と解析のオーバーライドの提案。ありがとう!
エドワードアンダーソン

11
実際のJSであなたの答えを得るとよいでしょう
Jason

6
感謝します。他の人は、js2coffee.org
ABCD.caを

16
質問が実際のJSである場合は、回答も必要です。
Manuel Hernandez


11

バックボーン自体にこれを行うための推奨される方法があるかどうかはわかりません。Layoutオブジェクトには独自のIDとバックエンドデータベースのレコードがありますか?もしそうなら、あなたはそれをあなた自身のモデルにすることができます。ない場合は、あなただけちょうどあなたがにし、適切にJSONからそれを変換することを確認し、ネストされた文書としてそれを残すことができますsaveし、parse方法。あなたがこのようなアプローチを取ることになる場合、私はあなたのAの例がバックボーンとより一貫していると思いますsetが正しく更新されるためとのattributesが、デフォルトでは、ネストされたモデルでバックボーンが何を行うかわかりません。これを処理するには、カスタムコードが必要になる可能性があります。


ああ!new演算子がありませんでした。この間違いを修正するために編集しました。
ロス・

ああ、それから私はあなたの質問を誤解しました。回答を更新します。
Peter Lyons

8

物事をシンプルに保つには、オプションBを使用します。

別の適切なオプションは、Backbone-Relationalを使用することです。次のように定義するだけです。

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

+1 Backbone-Releationalはかなり確立されているようです:自分のウェブサイト、1.6kスター、200 +フォーク。
ロス


5

rycfungの美しい答え CoffeeScriptバージョン:

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

甘くないですか?;)


11
JavaScriptで砂糖を使用しません:)
ロス

2

同じ問題があり、私はrycfungの回答のコードを実験してきました。これは素晴らしい提案です。
しかし、あなたがしたくない場合はset、直接ネストされたモデル、または絶えず渡したくない{parse: true}options、別のアプローチは、再定義することになりますset自分自身を。

ではバックボーン1.0.0setに呼ばれてconstructorunsetclearfetchおよびsave

モデルやコレクションをネストする必要があるすべてのモデルについて、次のスーパーモデルを検討してください。

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

とが意図的に空白になっていることmodelに注意してください。この抽象化レベルでは、おそらくコールバックに対して適切なアクションを定義することはできません。ただし、拡張するサブモデルでそれらをオーバーライドすることができます。 これらのコールバックは、たとえばリスナーをバインドしてイベントを伝播するのに役立ちます。_setModel_unsetModelCompoundModel
change


例:

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

これにより、ネストされたモデルが自動的に作成され、イベントが伝播されます。サンプルの使用法も提供され、テストされています。

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

出力:

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

私はこのパーティーに遅れていることに気づきましたが、このシナリオに正確に対処するためのプラグインを最近リリースしました。これはbackbone-nestifyと呼ばれます。

したがって、ネストされたモデルは変更されません。

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

次に、(Underscore.extendを使用して)包含モデルを定義するときにプラグインを使用します。

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

その後m、のインスタンスであるモデルがありImage、質問のJSONをに設定したとするとm、次のことができます。

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

バックボーンフォームを使用する

ネストされたフォーム、モデル、およびtoJSONをサポートしています。すべてネスト

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

あなたはまだ別のフレームワークを追加したくない場合は、オーバーライドされたとの基本クラスを作成することを検討可能性があるsettoJSONし、このようにそれを使用します。

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

あなたはBaseModelこの答えから必要になります(もしあなたが空想なら、要旨として利用可能です)。


1

私たちにもこの問題があり、チームワーカーがbackbone-nested-attributesという名前のプラグインを実装しました。

使い方はとても簡単です。例:

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

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

これにより、ツリーモデルは果物にアクセスできます。

tree.get('fruits')

あなたはここでより多くの情報を見ることができます:

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

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.