Backbone.js의 중첩 모델, 접근 방법


117

서버에서 제공되는 다음 JSON이 있습니다. 이를 통해 중첩 모델이있는 모델을 만들고 싶습니다. 나는 이것을 달성하는 방법이 확실하지 않습니다.

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

다음과 같은 구조를 가진 두 개의 중첩 된 백본 모델로 변환되기를 원합니다.

// structure
Image
    Layout
...

그래서 레이아웃 모델을 다음과 같이 정의합니다.

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

그러나 아래의 두 가지 (있는 경우) 기술 중 이미지 모델을 정의하는 데 사용해야하는 기술은 무엇입니까? 아래 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;
    }
});

모델 자체를 변경하지 않고 단순히 parse 메서드에서 원하는 개체를 다시 전달합니다.

이렇게하면 서버에서 읽을 때 중첩 된 모델의 구조가 보장됩니다. 이제 저장 또는 설정이 실제로 처리되지 않는 것을 알 수 있습니다. 적절한 모델을 사용하여 중첩 된 모델을 명시 적으로 설정하는 것이 합리적이라고 생각하기 때문입니다.

이렇게 :

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

또한 다음을 호출하여 중첩 된 모델에서 실제로 parse 메서드를 호출하고 있음을 유의하십시오.

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

model필드에 필요한만큼 중첩 된 모델을 정의 할 수 있습니다 .

물론 중첩 된 모델을 자체 테이블에 저장하려는 경우에도 마찬가지입니다. 이것으로는 충분하지 않습니다. 그러나 객체 전체를 읽고 저장하는 경우이 솔루션으로 충분합니다.


4
이것은 좋은 것입니다 .. 다른 접근 방식보다 훨씬 깨끗하기 때문에 받아 들여지는 대답이어야합니다. 내가 가진 유일한 제안은 가독성을 위해 Backbone.Model을 확장하는 클래스의 첫 글자를 대문자로 바꾸는 것입니다.. ie ImageModel 및 LayoutModel
Stephen Handley

1
@StephenHandley 의견과 제안에 감사드립니다. 정보를 위해 실제로 requireJS의 컨텍스트에서 이것을 사용하고 있습니다. 따라서 대문자 문제에 답하기 위해 var 'imageModel'이 실제로 requireJS로 반환됩니다. 그리고 모델에 대한 참조는 다음 구조로 캡슐화됩니다. define(['modelFile'], function(MyModel){... do something with MyModel}) 하지만 당신 말이 맞습니다. 나는 당신이 제안한 관례에 따라 모델을 참조하는 것을 습관으로 만듭니다.
rycfung 2012

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

예제 코드에 대한 Props 및 재정의 구문 분석 제안. 감사!
Edward Anderson

11
실제 JS에서 답을 가지고 좋은 것입니다
제이슨

6
coffeescript 버전이있어서 기쁩니다. 감사합니다. 다른 사람을 위해, 시도 js2coffee.org
ABCD.ca

16
질문이 진짜 JS라면 답도 있어야합니다.
Manuel Hernandez


11

Backbone 자체에 권장되는 방법이 있는지 잘 모르겠습니다. 레이아웃 개체가 백엔드 데이터베이스에 고유 한 ID와 레코드를 가지고 있습니까? 그렇다면 당신은 당신이 가지고있는 것처럼 그것을 자신의 모델로 만들 수 있습니다. 그렇지 않은 경우 중첩 된 문서로 남겨 둘 수 있으며 saveparse메서드 에서 JSON과 올바르게 변환되었는지 확인하십시오 . 이와 같은 접근 방식을 취하면 A 예제가 set제대로 업데이트 되기 때문에 백본과 더 일치 한다고 생각 attributes하지만 Backbone이 기본적으로 중첩 모델로 무엇을하는지 확실하지 않습니다. 이를 처리하려면 사용자 지정 코드가 필요할 수 있습니다.


아! 죄송합니다 new. 교환 원 이 누락되었습니다 . 이 실수를 수정하기 위해 편집했습니다.
Ross

아, 그럼 나는 당신의 질문을 잘못 해석했습니다. 내 대답을 업데이트하겠습니다.
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 개 이상의 포크와 같이 꽤 확립 된 것 같습니다.
Ross

6

중첩 된 모델과 속성에 Backbone DeepModel 플러그인을 사용합니다.

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

바인딩하여 이벤트를 n 레벨 깊이로 변경할 수 있습니다. 예를 들면 : model.on('change:example.nestedmodel.attribute', this.myFunction);


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에서 설탕을 사용하지 않습니다.
Ross

2

나는 같은 문제가 있었고 rycfung의 답변 에서 코드를 실험 해 왔는데 이는 훌륭한 제안입니다.
그러나 set중첩 된 모델을 직접 원하지 않거나를 지속적으로 전달하지 않으려 {parse: true}options경우 다른 접근 방식은 set자체 를 재정의하는 것입니다.

에서 백본 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

이 파티에 늦었다는 것을 알고 있지만 최근에 정확히이 시나리오를 처리하기 위해 플러그인을 출시했습니다. 이를 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

당신은 또 다른 프레임 워크를 추가하지 않으려면, 당신은 오버라이드 (override)과 기본 클래스를 생성하는 것이 좋습니다 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 모델은 과일에 액세스 할 수 있습니다.

tree.get('fruits')

여기에서 자세한 정보를 볼 수 있습니다.

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

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.