Mô hình lồng nhau trong Backbone.js, cách tiếp cận


117

Tôi đã cung cấp JSON sau đây từ một máy chủ. Với điều này, tôi muốn tạo một mô hình với một mô hình lồng nhau. Tôi không chắc đâu là cách để đạt được điều này.

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

Tôi muốn chúng được chuyển đổi thành hai mô hình xương sống lồng nhau với cấu trúc sau:

// structure
Image
    Layout
...

Vì vậy, tôi xác định mô hình Bố cục như sau:

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

Nhưng tôi nên sử dụng kỹ thuật nào trong hai (nếu có) dưới đây để xác định mô hình Hình ảnh? A hay B dưới đây?

A

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

hoặc, B

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

Câu trả lời:


98

Tôi gặp vấn đề tương tự khi đang viết ứng dụng Backbone của mình. Phải đối phó với các mô hình nhúng / lồng nhau. Tôi đã thực hiện một số chỉnh sửa mà tôi nghĩ là một giải pháp khá thanh lịch.

Có, bạn có thể sửa đổi phương thức phân tích cú pháp để thay đổi các thuộc tính xung quanh đối tượng, nhưng tất cả những điều đó thực sự là mã IMO khá không thể xác minh được và cảm thấy giống như một hack hơn là một giải pháp.

Đây là những gì tôi đề xuất cho ví dụ của bạn:

Đầu tiên hãy xác định Mô hình bố cục của bạn như vậy.

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

Sau đó, đây là mô hình hình ảnh của bạn:

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

Lưu ý rằng tôi đã không can thiệp vào chính mô hình, mà chỉ trả lại đối tượng mong muốn từ phương thức phân tích cú pháp.

Điều này sẽ đảm bảo cấu trúc của mô hình lồng nhau khi bạn đang đọc từ máy chủ. Bây giờ, bạn sẽ nhận thấy rằng việc lưu hoặc cài đặt thực sự không được xử lý ở đây vì tôi cảm thấy rằng bạn có thể đặt mô hình lồng nhau một cách rõ ràng bằng mô hình thích hợp.

Như vậy:

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

Cũng lưu ý rằng bạn thực sự đang gọi phương thức phân tích cú pháp trong mô hình lồng nhau của mình bằng cách gọi:

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

Bạn có thể xác định bao nhiêu mô hình lồng nhau trong modeltrường nếu bạn cần.

Tất nhiên, nếu bạn muốn lưu mô hình lồng nhau trong bảng của chính nó. Điều này sẽ không đủ. Nhưng trong trường hợp đọc và lưu toàn bộ đối tượng, giải pháp này là đủ.


4
Điều này thật tuyệt .. nên là câu trả lời được chấp nhận vì nó rõ ràng hơn nhiều so với các cách tiếp cận khác. Chỉ gợi ý tôi muốn có là để tận dụng các chữ cái đầu tiên của lớp học của bạn mà mở rộng Backbone.Model để có thể đọc .. tức là ImageModel và LayoutModel
Stephen Handley

1
@StephenHandley Cảm ơn nhận xét và đề xuất của bạn. Để biết thông tin, tôi thực sự đang sử dụng nó trong ngữ cảnh của requestJS. Vì vậy, để trả lời cho vấn đề viết hoa, var 'imageModel' thực sự được trả lại cho requestJS. Và tham chiếu đến mô hình sẽ được gói gọn bởi cấu trúc sau: define(['modelFile'], function(MyModel){... do something with MyModel}) Nhưng bạn nói đúng. Tôi thực sự tạo thói quen tham khảo mô hình theo quy ước mà bạn đề xuất.
rycfung

@BobS Xin lỗi, là một lỗi đánh máy. Đáng lẽ phải được phản hồi. Tôi đã sửa nó, cảm ơn vì đã chỉ ra.
rycfung

2
Đẹp! Tôi khuyên bạn nên thêm điều này vào Backbone.Model.prototype.parsechức năng. Sau đó, tất cả những gì mô hình của bạn phải làm là xác định các loại đối tượng mô hình con (trong thuộc tính "model" của bạn).
jasop

1
Mát mẻ! Tôi đã làm điều gì đó tương tự (đáng chú ý và đáng tiếc là sau khi tôi tìm thấy câu trả lời này) và viết nó lên đây: blog.untrod.com/2013/08/decl Compare-approach-to-nesting.html Sự khác biệt lớn là đối với các mô hình lồng nhau sâu Tôi khai báo toàn bộ ánh xạ cùng một lúc, trong mô hình gốc / mẹ, và mã lấy nó từ đó và đi xuống toàn bộ mô hình, đưa các đối tượng có liên quan vào các bộ sưu tập và mô hình Backbone. Nhưng thực sự là một cách tiếp cận rất giống nhau.
Chris Clark

16

Tôi đăng mã này như một ví dụ về đề xuất của Peter Lyon để xác định lại phân tích cú pháp. Tôi đã có cùng một câu hỏi và điều này đã làm việc cho tôi (với phần phụ trợ Rails). Mã này được viết bằng Coffeescript. Tôi đã đưa ra một số điều rõ ràng cho những người không quen thuộc với nó.

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

hoặc, trong 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;
};

Đạo cụ cho mã ví dụ và đề xuất phân tích cú pháp ghi đè. Cảm ơn!
Edward Anderson

11
Sẽ thật tuyệt khi có câu trả lời trong JS thực
Jason

6
rất vui khi có phiên bản coffeescript, cảm ơn. Đối với những người khác, hãy thử js2coffee.org
ABCD.ca

16
Nếu câu hỏi là JS thực, một câu trả lời cũng phải như vậy.
Manuel Hernandez


11

Tôi không chắc bản thân Backbone có một cách được khuyến nghị để làm điều này. Đối tượng Bố trí có ID riêng của nó và bản ghi trong cơ sở dữ liệu phía sau không? Nếu vậy, bạn có thể biến nó thành Model của riêng nó như bạn có. Nếu không, bạn có thể để nó dưới dạng tài liệu lồng nhau, chỉ cần đảm bảo bạn chuyển đổi nó sang và từ JSON đúng cách trong phương thức saveparse. Nếu bạn thực hiện cách tiếp cận như thế này, tôi nghĩ ví dụ A của bạn phù hợp hơn với backbone vì setsẽ cập nhật đúng cách attributes, nhưng một lần nữa tôi không chắc Backbone làm gì với các mô hình lồng nhau theo mặc định. Có thể bạn sẽ cần một số mã tùy chỉnh để xử lý việc này.


Ah! Xin lỗi, nó đã bị thiếu newnhà điều hành. Tôi đã chỉnh sửa nó để sửa lỗi này.
Ross

Ồ, vậy thì tôi đã hiểu sai câu hỏi của bạn. Tôi sẽ cập nhật câu trả lời của tôi.
Peter Lyons

8

Tôi sẽ chọn Lựa chọn B nếu bạn muốn mọi thứ đơn giản.

Một lựa chọn tốt khác là sử dụng Backbone-Relational . Bạn chỉ cần xác định một cái gì đó như:

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

+1 Backbone-Releational có vẻ khá ổn định: trang web riêng, 1,6 nghìn sao, hơn 200 nhánh.
Ross

6

Tôi sử dụng plugin Backbone DeepModel cho các mô hình và thuộc tính lồng nhau.

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

Bạn có thể liên kết để thay đổi độ sâu của các sự kiện. ví dụ: model.on('change:example.nestedmodel.attribute', this.myFunction);


5

Phiên bản CoffeeScript của câu trả lời tuyệt vời của 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

Không ngọt sao? ;)


11
Tôi không sử dụng đường trong JavaScript của mình:)
Ross

2

Tôi đã gặp vấn đề tương tự và tôi đang thử nghiệm với mã trong câu trả lời của rycfung , đó là một gợi ý tuyệt vời.
Tuy nhiên, nếu bạn không muốn settrực tiếp đến các mô hình lồng nhau hoặc không muốn liên tục chuyển {parse: true}vào options, một cách tiếp cận khác sẽ là xác định lạiset bản thân.

Trong Backbone 1.0.0 , setđược gọi trong constructor, unset, clear, fetchsave .

Hãy xem xét siêu mô hình sau đây , cho tất cả các mô hình cần lồng mô hình và / hoặc bộ sưu tập.

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

Lưu ý rằng model, _setModel_unsetModelđược để trống có mục đích. Ở mức trừu tượng này, bạn có thể không thể xác định bất kỳ hành động hợp lý nào cho các lệnh gọi lại. Tuy nhiên, bạn có thể muốn ghi đè chúng trong các mô hình con mở rộng CompoundModel.
Ví dụ, các lệnh gọi lại đó rất hữu ích để ràng buộc người nghe và tuyên truyền changecác sự kiện.


Thí dụ:

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

Với điều này, bạn có thể tự động tạo mô hình lồng nhau và truyền sự kiện. Việc sử dụng mẫu cũng được cung cấp và thử nghiệm:

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

Đầu ra:

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

Tôi nhận ra mình đến muộn với bữa tiệc này, nhưng gần đây chúng tôi đã phát hành một plugin để giải quyết chính xác tình huống này. Nó được gọi là backbone-nestify .

Vì vậy, mô hình lồng nhau của bạn vẫn không thay đổi:

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

Sau đó, sử dụng plugin khi xác định mô hình chứa (sử dụng Underscore.extend ):

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

Sau đó, giả sử bạn có một mô hình mlà một phiên bản của Imagevà bạn đã đặt JSON từ câu hỏi trên m, bạn có thể là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

Sử dụng các biểu mẫu xương sống

Nó hỗ trợ các biểu mẫu, mô hình và toJSON lồng nhau. TẤT CẢ NESTED

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

Nếu bạn không muốn thêm chưa khuôn khổ khác, bạn có thể xem xét việc tạo ra một lớp cơ sở với ghi đè settoJSONvà sử dụng nó như thế này:

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

Bạn sẽ cần BaseModelcâu trả lời này (có sẵn, nếu bạn thích, như một ý chính ).


1

Chúng tôi cũng gặp sự cố này và một nhân viên nhóm đã triển khai một plugin có tên là thuộc tính xương sống (backbone-nested-properties).

Cách sử dụng rất đơn giản. Thí dụ:

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

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

Với điều này, mô hình Cây có thể truy cập vào các kết quả sau đó:

tree.get('fruits')

Bạn có thể xem thêm thông tin tại đây:

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

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.