Nested Models ใน Backbone.js วิธีการเข้าใกล้


117

ฉันได้รับ JSON ต่อไปนี้จากเซิร์ฟเวอร์ ด้วยเหตุนี้ฉันจึงต้องการสร้างโมเดลที่มีโมเดลซ้อนกัน ฉันไม่แน่ใจว่าวิธีใดที่จะบรรลุสิ่งนี้

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

ฉันต้องการให้สิ่งเหล่านี้ถูกแปลงเป็นแบบจำลองกระดูกสันหลังที่ซ้อนกันสองแบบโดยมีโครงสร้างต่อไปนี้:

// structure
Image
    Layout
...

ดังนั้นฉันจึงกำหนดรูปแบบเค้าโครงดังนี้:

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

แต่สองเทคนิคใด (ถ้ามี) ด้านล่างนี้ที่ฉันควรใช้เพื่อกำหนดรูปแบบรูปภาพ A หรือ B ด้านล่าง?

A

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 ที่ไม่สามารถเข้าถึงได้จริง ๆ และรู้สึกว่าเป็นการแฮ็กมากกว่าวิธีแก้ปัญหา

นี่คือสิ่งที่ฉันแนะนำสำหรับตัวอย่างของคุณ:

ก่อนอื่นให้กำหนด Layout Model ของคุณเช่นนั้น

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

โปรดทราบว่าคุณกำลังเรียกใช้วิธีการแยกวิเคราะห์ในโมเดลที่ซ้อนกันของคุณโดยการเรียก:

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

คุณสามารถกำหนดโมเดลที่ซ้อนกันในmodelฟิลด์ได้มากเท่าที่คุณต้องการ

แน่นอนว่าหากคุณต้องการไปไกลถึงการบันทึกโมเดลที่ซ้อนกันในตารางของมันเอง สิ่งนี้จะไม่เพียงพอ แต่ในกรณีของการอ่านและบันทึกวัตถุโดยรวมการแก้ปัญหานี้ควรเพียงพอ


4
นี่เป็นสิ่งที่ดี .. ควรเป็นคำตอบที่ยอมรับได้ว่าสะอาดกว่าวิธีอื่น ๆ ข้อเสนอแนะเท่านั้นที่ฉันมีคือใช้อักษรตัวพิมพ์ใหญ่ตัวแรกของชั้นเรียนของคุณที่ขยาย Backbone โมเดลสำหรับการอ่าน .. เช่น 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ฟังก์ชัน จากนั้นโมเดลทั้งหมดของคุณต้องทำคือกำหนดประเภทอ็อบเจ็กต์โมเดลย่อย (ในแอตทริบิวต์ "model" ของคุณ)
jasop

1
เย็น! ฉันทำบางสิ่งที่คล้ายกัน (โดยเฉพาะอย่างยิ่งและน่าเสียใจหลังจากที่ฉันพบคำตอบนี้) และเขียนมันไว้ที่นี่: blog.untrod.com/2013/08/declarative-approach-to-nesting.htmlความแตกต่างใหญ่คือสำหรับโมเดลที่ซ้อนกันอย่างลึกซึ้ง ฉันประกาศการทำแผนที่ทั้งหมดพร้อมกันในโมเดลรูท / พาเรนต์และโค้ดจะนำมันมาจากที่นั่นและเดินลงไปทั้งโมเดลโดยให้ความชุ่มชื้นแก่วัตถุที่เกี่ยวข้องในคอลเลกชันและโมเดล Backbone แต่วิธีการที่คล้ายกันมาก
Chris Clark

16

ฉันโพสต์โค้ดนี้เป็นตัวอย่างคำแนะนำของ Peter Lyon ในการกำหนดนิยามใหม่ของการแยกวิเคราะห์ ฉันมีคำถามเดียวกันและสิ่งนี้ใช้ได้กับฉัน (พร้อมกับแบ็กเอนด์ 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;
};

อุปกรณ์ประกอบสำหรับโค้ดตัวอย่างและแนะนำการลบล้างการแยกวิเคราะห์ ขอบคุณ!
Edward Anderson

11
จะเป็นการดีที่จะได้รับคำตอบของคุณใน JS จริง
Jason

6
มีความสุขที่มีเวอร์ชั่นกาแฟขอบคุณ สำหรับคนอื่น ๆ ลองใช้js2coffee.org
ABCD.ca

16
หากคำถามเป็น JS จริงคำตอบก็ควรเป็นเช่นกัน
Manuel Hernandez

12

ใช้Backbone.AssociatedModelจากการเชื่อมโยงกระดูกสันหลัง :

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

ทางออกที่ดี มีโครงการคล้าย ๆ กันอยู่ที่นั่นด้วย: github.com/PaulUithol/Backbone-relational
michaelok

11

ฉันไม่แน่ใจว่า Backbone มีวิธีที่แนะนำในการทำเช่นนี้ วัตถุ Layout มี ID ของตัวเองและบันทึกในฐานข้อมูลส่วนหลังหรือไม่ ถ้าเป็นเช่นนั้นคุณสามารถสร้างเป็น Model ของตัวเองได้ตามที่คุณมี หากไม่เป็นเช่นนั้นคุณสามารถปล่อยให้เป็นเอกสารซ้อนกันได้เพียงตรวจสอบให้แน่ใจว่าคุณแปลงเป็นและจาก JSON อย่างถูกต้องในsaveและparseวิธีการ หากคุณไม่จบลงด้วยการใช้วิธีเช่นนี้ผมคิดว่าคุณตัวอย่างคือสอดคล้องกับกระดูกสันหลังตั้งแต่ถูกต้องจะอัปเดตอีกครั้ง แต่ผมไม่แน่ใจว่าสิ่งที่แกนนำจะมีรุ่นที่ซ้อนกันโดยค่าเริ่มต้น เป็นไปได้ว่าคุณจะต้องใช้โค้ดที่กำหนดเองเพื่อจัดการสิ่งนี้setattributes


อา! ขออภัยไม่มีตัวnewดำเนินการ ฉันได้แก้ไขเพื่อแก้ไขข้อผิดพลาดนี้
รอส

โอ้ฉันแปลคำถามของคุณผิด ฉันจะอัปเดตคำตอบของฉัน
Peter Lyons

8

ฉันจะใช้ตัวเลือก B ถ้าคุณต้องการให้ทุกอย่างเรียบง่าย

ตัวเลือกที่ดีอีกก็จะไปใช้โครงสัมพันธ์ คุณแค่กำหนดบางสิ่งเช่น:

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

+1 Backbone-Releational ดูเหมือนจะเป็นที่ยอมรับ: เว็บไซต์ของตัวเอง, 1.6k ดาว, 200+ ส้อม
รอส

6

ฉันใช้ปลั๊กอิน Backbone DeepModel สำหรับโมเดลและแอตทริบิวต์ที่ซ้อนกัน

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

คุณสามารถเชื่อมโยงเพื่อเปลี่ยนระดับ n เหตุการณ์ได้ ตัวอย่างเช่น: model.on('change:example.nestedmodel.attribute', this.myFunction);


5

คำตอบที่สวยงามของเวอร์ชัน CoffeeScript ของ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

ไม่หวานเหรอ? ;)


11
ฉันไม่ใช้น้ำตาลใน JavaScript ของฉัน :)
รอส

2

ฉันมีปัญหาเดียวกันและฉันได้ทดลองใช้โค้ดในคำตอบของ rycfungซึ่งเป็นคำแนะนำที่ดี
แต่ถ้าคุณไม่ต้องการที่จะsetรุ่นที่ซ้อนกันโดยตรงหรือไม่ต้องการที่จะผ่านอย่างต่อเนื่อง{parse: true}ในoptionsวิธีการอื่นที่จะ redefine setตัวเอง

ในBackbone 1.0.0 , setถูกเรียกในconstructor, unset, clear, และfetchsave

พิจารณาซูเปอร์โมเดลต่อไปนี้สำหรับทุกรุ่นที่ต้องซ้อนโมเดลและ / หรือคอลเลกชัน

/** 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และ_unsetModelจะเว้นว่างไว้ในวัตถุประสงค์ ในระดับนามธรรมนี้คุณอาจกำหนดการกระทำที่สมเหตุสมผลสำหรับการเรียกกลับไม่ได้ อย่างไรก็ตามคุณอาจต้องการลบล้างในรุ่นย่อยที่ขยายออกCompoundModelไป
ตัวอย่างเช่นการเรียกกลับเหล่านั้นมีประโยชน์ในการผูกผู้ฟังและเผยแพร่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

ฉันรู้ว่าฉันมาสายปาร์ตี้นี้ แต่เราเพิ่งเปิดตัวปลั๊กอินเพื่อจัดการกับสถานการณ์นี้ มันเรียกว่ากระดูกสันหลัง nestify

ดังนั้นโมเดลที่ซ้อนกันของคุณจะไม่เปลี่ยนแปลง:

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

Then use the plugin when defining the containing model (using 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

หากคุณไม่ต้องการที่จะเพิ่มยังกรอบอื่นคุณอาจพิจารณาการสร้างชั้นฐานกับแทนที่setและtoJSONและใช้งานได้เช่นนี้

// 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 จึงสามารถเข้าถึงผลไม้ได้:

tree.get('fruits')

คุณสามารถดูข้อมูลเพิ่มเติมได้ที่นี่:

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

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.